概述
这里是记录一些本人在看书或是开发过程中遇到的一些数据库的性能优化问题,希望与君共勉。
版权说明
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者:Coding-Naga
发表日期: 2016年4月5日
本文链接:http://blog.csdn.net/lemon_tree12138/article/details/51062548
来源:CSDN
更多内容:分类 >> 数据库
奇技淫巧
0. 提前终止查询
这一点可能注意过的人并不多,我现在假设我的数据表中有一label字段,假设我现在查询的值在数据表中只有一份,而且比较靠前。SQL语句如下:
SELECT * FROM labels WHERE label='iwpfdc';
查询的结果如下:
+----+--------+
| id | label |
+----+--------+
| 25 | iwpfdc |
+----+--------+
1 row in set (22.13 sec)
查询上面的结果,竟然耗费了22秒!这是一个恐怖的事情。而且可以看到id只有25,说明,这是一个比较靠前的数据。这是可怕的,因为考虑到这条数据可能只有一条。可是,上面的查询语句会对整个数据表进行全表扫描。为了防止全表扫描,我们可以在查到了一条数据时,就中止查询。所以这里我们可以使用LIMIT 1来提前终止查询。
SELECT * FROM labels WHERE label='iwpfdc' LIMIT 1;
现在来看结果:
+----+--------+
| id | label |
+----+--------+
| 25 | iwpfdc |
+----+--------+
1 row in set (0.10 sec)
只有0.1秒了,时间是可观的。不过这还不是最佳处理,还有一些更好的操作请继续阅读。
1. 为搜索字段创建索引
这一点很重要,在平常的项目开发中,为搜索字段创建索引是必不可少的。不过我们了不能为了创建索引而创建索引,也不要为所有的列都创建索引,毕竟数据库在对索引的维护上也是有时间消耗的。下面暂且只说明索引带来的好处。
还是上面的数据库,在对label列创建完索引之后,再一次查询时,结果有了明显地提升。
查询SQL语句
select * from labels where label='yjrplewcft';
查询结果
+----+------------+
| id | label |
+----+------------+
| 35 | yjrplewcft |
+----+------------+
1 row in set (0.11 sec)
上面是在进行“全表”扫描的结果,虽然如此,可是速度还是很快。下面就第1条优化建议进行查询,看看结果。
查询SQL语句
SELECT * FROM labels WHERE label='yjrplewcft' LIMIT 1;
查询结果
+----+------------+
| id | label |
+----+------------+
| 35 | yjrplewcft |
+----+------------+
1 row in set (0.03 sec)
这里只用了0.03秒就完成了查询。由此可见,索引和提前终止查询在实际应用中的重要性。
2. 使用EXPLAIN
EXPLAIN在对SQL语句的优化上有指导性的意义,这很重要。比如下面的这条SQL语句:
EXPLAIN SELECT * FROM labels WHERE label='yjrplewcft' LIMIT 1;
下面是EXPLAIN的结果:
+----+-------------+--------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | labels | NULL | ref | index_labels | index_labels | 92 | const | 1 | 100.00 | Using index |
+----+-------------+--------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.15 sec)
从上面可以很直观地看到,type、rows、filtered、Extra等列的数据都是最佳的,由此可得出此条查询语句是一条性能比较好的SQL语句。
本人在《MySQL多表查询核心优化》文章中也有一些具体的实战示例,感兴趣的同学可以进行参考。
3. 避免返回不需要的列
其实在实际的查询中很少有可能需要查询数据表中所有的列,那么就需要有选择地进行选择字段。所以在你的项目里请避免使用 SELECT * 。
从数据库里读出越多的数据,那么查询就会变得越慢。尤其在需要经过网络传输时,这就会大大地增加网络传输的负载。
所以,在实际的开发过程中,还是只查询自己当前需要的东西,避免使用 SELECT * 吧。
4. 尽可能地使用 NOT NULL
这一点对于新手来说可能也不是太了解,本人也是最近才了解到这一点。因为在MySQL中对NULL字段处理的特殊性,致使我们需要对NULL字段进行格外关照。
首先,我们要知道在MySQL中,NULL值并非我们一般意义上的空值。他是占用空间的,可以通过下面的查询了解:
SELECT LENGTH(''), LENGTH(' '), LENGTH(NULL);
+------------+-------------+--------------+
| LENGTH('') | LENGTH(' ') | LENGTH(NULL) |
+------------+-------------+--------------+
| 0 | 1 | NULL |
+------------+-------------+--------------+
1 row in set (0.00 sec)
其次,虽然NULL占用了空间,可是MySQL却并不为NULL字段创建索引。
5. 尽可能地使用定长的字段
这一点要怎么理解呢?如果你学习或是了解过操作系统中的地址偏移量的概念,那么这个技巧你应该会很快就理解了。
固定长度的表会提高性能,MySQL在搜寻的时候也会更快一些。因为这些固定的长度是很容易就可以计算下一个数据的偏移量,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序先找到主键。
6. 不要以字符串定义IP地址
IP地址的形式一般就是这样的:127.100.100.101。而根据上面第5点的技巧,我们也可以很快对这一字段有一个很好的设计,就像这样:
ip VARCHAR(15) NOT NULL
是的,你看已经对字段进行了定长的设计,这个很合理呀。可是,如果你了解IP地址是可以进行整型转换的话,那么这样的设计显然有点逊色了。我们可以将这个字段定义成UNSIGNED INT或是LONG。
将IP定义成整型的好处在于,方便地对其创建索引,还可以方便地进行范围查询:
... WHERE ip BETWEEN ip1 AND ip2
这一点在实际的项目开发中使用得还是比较多的。
7. 尽量避免使用ORDER BY
在MySQL中,对结果进行排序是很耗时间的。我们还可是通过一个实例来说明会更加直观一些。
SELECT * FROM labels ORDER BY label LIMIT 200000, 5;
+---------+------------------------+
| id | label |
+---------+------------------------+
| 3182819 | anjmlhwztqbu |
| 1654768 | anjmlrf |
| 6719238 | anjmopcfeydwkubtqhgxlr |
| 9301930 | anjmq |
| 3042587 | anjmqhxbzyrkpvswlto |
+---------+------------------------+
SELECT * FROM labels LIMIT 200000, 5;
+---------+------------------------+
| id | label |
+---------+------------------------+
| 3182819 | anjmlhwztqbu |
| 1654768 | anjmlrf |
| 6719238 | anjmopcfeydwkubtqhgxlr |
| 9301930 | anjmq |
| 3042587 | anjmqhxbzyrkpvswlto |
+---------+------------------------+
5 rows in set (0.08 sec)
所以,还是在不需要排序的时候避免使用ORDER BY吧。
8. 大量数据的情况下,尽量使用分页查询
这一条其实与第3条很类似,一个是从纵向的角度切分,一个是从横向的角度进行切分。对于分页的使用在第7条中也有实例应用,可以参见。
9. 在要求不是很苛刻的情况下,可以使用max(id)代替count(*)
对于存在大量数据的情况下,如果我们需要查询当前数据表中有多少条记录,这个要怎么做呢?
很容易想到的是使用COUNT()函数来完成,可是对于数据量很大的情况下,这里的查询会很慢。因为我们在设计数据表的时候,一般会为数据表设计一个id字段,这个字段会随着数据的添加而自增。所以,如果我们对于查询结果只是想有一个粗略地了解的话,我们完全可以采用MAX()函数查询。下面就是实际的应用:
COUNT()
SELECT COUNT(*) FROM labels;
查询结果
+----------+
| COUNT(*) |
+----------+
| 10534997 |
+----------+
1 row in set (5.04 sec)
MAX(id)
SELECT MAX(id) FROM labels;
查询结果
+----------+
| MAX(id) |
+----------+
| 10537190 |
+----------+
1 row in set (0.00 sec)
可以看到这里的查询结果是有很明显地悬殊的。所以如果你对查询结果并没有很严格的要求的话,建议采用MAX(id)来查询。
Ref
- 《高性能MySQL(第3版).Baron.Scbwartz》
- 《MySQL技术内幕 InnoDB存储引擎 第2版》
- http://www.bitscn.com/pdb/mysql/201007/188342.html