高性能MySQL - 查询性能优化

本文来源 《高性能MySQL》sql

1、如何检查一个查询的好坏

查询性能低下最基本的缘由是访问的数据太多。形成低效查询的缘由有如下两个:缓存

1. 检索大量不须要的数据。服务器

2. MySQL服务层在分析大量超过须要的数据行。cookie


1. 检索大量不须要的数据

1.1 查询不须要的记录

一个常见的错误是误觉得MySQL会只返回须要的数据,实际上MySQL是先返回所有结果集再进行运算。 e.g. 若是只须要在页面显示10个数据,最简单有效的方法是加LIMIT,而不是查出整个结果集再抛弃。

1.2 返回多余的列

高性能MySQL书中把这个分红 多表关联时返回所有列 和 老是取出所有列。 我的感受能够归成一列。多表关联时候,若是咱们要的只是其中一个表的数据,就直接 ”SELECT 表名.* “ 不要直接用 * 。在使用select * 时要注意是否是真的须要所有数据。 取出所有列,会让优化器没法完成索引覆盖扫描这类优化,并且给服务器带来额外的I/O,内存,CPU的消耗。

1.3 重复查询相同的数据

这个主要是开发人员应多多注意的事情,在开发某个模块,好比评论时,须要查询用户头像URL,用户屡次评论的时候,可能会重复查询。最好是使用变量存储,对于使用频率特别高的,甚至能够用session,cookie来存放。

2. MySQL扫描额外的记录

MySQL简单衡量查询开销的三个指标:session

2.1 响应时间

响应时间包括服务时间和等待时间,但这两个时间并不能细分出来,因此响应时间受影响比较大。咱们能够经过估计查询的响应时间来作最初步的判断。

2.2 扫描的行数

2.3 返回的行数


2、MySQL查询执行基础

MySQL  客户端和服务端通讯协议是“半双工”的,这就意味着,客户端发送给服务器和服务器发给客户端是不能同时发生,这种协议让MySQL通讯简单快速,但也就没法进行流量控制,一旦一端开始了,另外一端是能等它结束。因此查询语句很长的时候,参数max_allowed_packet就特别重要了。

当想MySQL发送一个请求的时候,MySLQ到底作了什么: 
1. 客户端发送一个查询给服务器。
2. 服务器先检查查询缓存,若是命中,就马上返回存储在缓存中的结果,不然进入下一个阶段。
3. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
4. 把查询的结果返回给客户端。

MySQL(优化器)能处理的优化类型:

1. 从新定义关联表的顺序。
2. 将外链接转化成内链接。
3. 使用等价变换规则。
能够移除一些恒成立和一些恒不成立的判读。例如(5 = 5 AND a > 5) => a > 5
4. 优化COUNT()、MIN() 和 MAX()
例如找到某一列的最小值,只须要查询对应B-Tree索引最左端的记录。
5. 覆盖索引扫描
索引中的列包含全部查询中须要的列的时候,只须要使用索引返回数据,不须要搜索数据行
6. 子查询优化
MySQL能够将子查询转换一种效率更高的形式,从而减小多个查询屡次对数据进行访问。
7. 提早终止查询
当发现已经知足查询需求的时候,MySQL老是可以马上终止查询。
8. 等值传播
若是两个列的值经过等式关联,那么MySQL可以把其中一个列的WHERE条件传递到另外一列中。
9. 列表IN()的比较
MySQL中IN()列表中的数据线进行排序,而后经过二分查找的方式来肯定列表中的值是否知足条件,是O(lgn)级别的操做,等价转换成OR查询的复杂度是O(n)

MySQL如何执行关联?

MySQL中关联不只仅是一个查询须要到两个表匹配才叫关联,每个查询,每个片断(包括子查询,甚至基于单表的SELECT)均可能是关联。

MySQL采用嵌套循环关联操做进行关联。

具体作法:1. MySQL先从一个表中循环取出单条数据,而后嵌套循环到下一个表中寻找匹配的行,依次下去,直到全部表中匹配的行为止。 
2. 根据各个表匹配的行,返回查询中须要的各个列。
MySQL会尝试在最后一个关联表中找到全部匹配的行,若是最后一个关联表没法找到更多的行之后,MySQL返回上一层次关联表。看是否可以找到,依次类推。

3、MySQL 查询优化器的局限性

1. 关联子查询

MySQL的子查询实现得很是糟糕。最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。 

e.g. 
 
 
SELECT * FROM sakia.film WHERE film_id IN( SELECT  film_id FROM sakia.film_actor WHERE actor_d = 1)
咱们但愿MySQL可以先执行内层子查询,这个子查询经过索引来查找,应该会很快,事实上,MySQL 并不这么干,它会把从查询这样优化:
SELECT * FROM sakia.film WHERE EXIST (SELECT * FROM sakia.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id);
MySQL把外层压人到子查询中处理,若是外层的表是很是大的表的话,这个查询的性能就会很是的糟糕。

改进方案:
1.  
SELECT film.* FROM sakila.film INNER JOIN sakila.film_actor USING(film_id) WHERE actor_id = 1;
2. 
SELECT *FROM sakia.film WHERE EXISTS( SELECT * FROM sakia.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id);

如何用好关联子查询

并非全部的关联子查询的性能都不好,不少时候,关联子查询是一种很是合理,天然,甚至是性能最好的写法。
 
 
EXPLAIN SELECT film_id, language_id FROM sakila.film WHERE NOT EXISTS(
	SELECT * FROM sakia.film_actor
	WHERE film_actor.film.id = film.film_id
);
 这个查询并不比左外链接的差,但这最好是要通过测试来验证的。 
 
 

4、优化特定类型的查询

1. 优化COUNT()查询

COUNT 要注意的就两点:
1. COUNT统计列值要求列值是非空的(不统计NULL)。
2. COUNT(*)统计的是结果集的行数。若是但愿知道的是结果集的行数,最好使用COUNT(*),这样写意义清晰,性能也会很好。

一个容易产生误解的是:MyISAM 的 COUNT()函数老是很是快的,实际上只有没有WHERE条件的COUNT(*)才很是快,由于此时无须实际地去计算表的行数。MySQL能够利用存储引擎的特性直接得到这个值。当统计带WHERE子局的结果集行数,能够是统计某个列值的数量时,MyISAM的COUNT()和其余存储引擎没有任何不一样。

PS:使用COUNT的时候能够利用MyISAM这个特性来加快查询。好比,当查询  SELECT COUNT(*) FROM world.City WHERE ID > 5; 能够改为 SELECT (SELECT COUNT(*) FROM world.City - COUNT(*) FROM world.City WHERE ID <= 5;

使用精确值来估算,执行EXPLAIN并不须要真正去执行查询,成本很低。好比那些每30分钟统计一下大概的在线人数,就能够用这种估算的方式来优化。

当count()自己须要扫描大量的行才能获取精确的结果,优化是很是难的。在MySQL的层面上能作的就只有索引覆盖扫描了,若是还不够,则须要考虑修改应用的框架。

2. 优化关联查询

1. 确保ON和USING子句中的列上有索引。在建立索引的时候就要考虑到关联的顺序。当表A和表B用c关联的时候,若是优化器的关联顺序是B、A,那么久不须要再B表的对应列上建索引了。只须要在关联顺序中第二个表的相应列上建立索引。

2. 确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化这个过程。

3. 优化子查询

尽量使用关联查询代替子查询,至少当前的MySQL版本须要这样。但尽量不是绝对,通过测试的才是硬道理。

4. 优化GROUP BY 和 DISTINCT

MySQL使用索引来优化这两种查询。没法使用索引的时候,GROUP BY使用两种策略来完成:使用临时表或者文件排序来作分组。
SELECT actor.first_name, actor.last_name,COUNT(*)
FROM sakila.film_actor
        INNER JOIN sakia.actor USING(actor_id)
GROUP BY actor.first_name, actor.last_name;

SELECT actor.first_name, actor.last_name, COUNT(*)
FROM sakia.film_actor
        INNER JOIN sakila.actor USING(actor_id)
GROUP BY film_actor.actor_id;
使用第二段SQL效率比第一段要高。

5. 优化 LIMIT 分页

一般会使用LIMIT加上偏移量的方法实现,同时加上合适的ORDER BY的子句。若是有对应的索引,一般效率会不错。

在偏移量很是大的时候,例如,LIMIT 1000,20这样的查询,须要查找10020条记录而后返回20条,前面10000条都将被抛弃,这样的代价很是高。优化此类分页查询的一个最简单的方法就是尽量使用索引覆盖扫描,而不是查询全部的列。例如:
SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50,5;
若是这个表很是大,创建改为:
SELECT film.film_id,film.description
FROM sakia.film
        INNER JOIN (
              SELECT film_id FROM sakia.film
              ORDER BY LIMIT 50,5
        ) AS lim USING(film_id);
这里的延迟关联大大提高查询效率,让MySQL扫描尽量少的页面。
有时候也能够将LIMIT查询转换为已知位置的查询,让MySQL经过范围扫描得到到对应的结果。例如,若是一个位置列上有索引,而且预先计算出了边界值,则能够直接定位。

6. 优化UNION查询

除非须要服务器消除重复的行,不然就必定要使用UNION ALL,这一点很重要。若是没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会致使对整个临时表的数据作惟一性检查。


以上是《高性能MySQL》一书中关于的总结,但总结的这些感受仍是不够用,在接下来我会根据网上别人的经验总结一些, 来源 http://database.51cto.com/art/201407/445934.htm。框架


1. 对查询进行优化,要尽可能避免全表扫描,首先应考虑在 where 及 order by 涉及的列上创建索引。函数


2. 应尽可能避免在 where 子句中对字段进行 null 值判断,不然将致使引擎放弃使用索引而进行全表扫描。这个在上面也有说过,尽可能不要设置NULL值。性能


3. 应尽可能避免在 where 子句中使用 != 或 <> 操做符,不然将引擎放弃使用索引而进行全表扫描。
测试


4.  应尽可能避免在 where 子句中使用 or 来链接条件,能够改成 union all 来查询

5. in 和 not in 也要慎用,不然会致使全表扫描,对于连续的数值能用between就不用in,或者使用exists来代替,如:
SELECT num FROM a WHERE num in(SELECT num FROM b)
能够用exists来替换:
SELECT num FROM a WHERE EXISTS (SELECT 1 FROM b WHERE num = a.num)
6. 模糊搜索也会致使全表搜索,能够经过创建全文索引来提升效率。 

7. 若是在 where 子句中使用参数,也会致使全表扫描,能够强制查询使用索引
SELECT id FROM t wiith( index(索引名)) WHERE num = @num

8. 避免在where子句对字段进行表达式操做。

9. 应尽可能避免在where子句中对字段进行函数操做,这将致使引擎放弃使用索引而进行全表扫描。如:
SELECT id from t where datediff(day,createdate,'2015-11-30')  = 0
应改为:
SELECT id FROM t where createdate >= '2015-11-30' and createdate < '2015-12-1'

10. Update 语句,若是只更改一、2个字段, 不要Update所有字段 ,不然频繁调用会引发明显的性能消耗,同时带来大量日志。

11.  对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,不然逻辑读会很高,性能不好。

12 .尽可能避免使用游标,由于游标的效率较差,若是游标操做的数据超过1万行,那么就应该考虑改写