为什么查询会慢?
响应时间、扫描的行数、返回的行数,是衡量查询开销的三个指标(记录在慢日志中)
-
查询了不需要的数据
- 查询不需要的记录:比如查询了100条记录到应用,取其中的10条显示,最好的解决办法是加上LIMIT
- 多表关联时返回全部列:应该只取需要的列,否则会返回所有关联表的所有记录
- 总是取出全部列:比如SELECT * FROM xxx
- 重复查询相同的数据:最好使用缓存
-
扫描了额外的记录
Explain语句中的type列的类型:
- 全表扫描
- 范围扫描
- 唯一索引查询
- 常数引用
MySQL能够使用三种方式应用Where过滤条件,从好到坏依次为:
- 在索引中使用Where条件来过滤不匹配的记录。这是在储存引擎层完成的
- 使用覆盖索引扫描(Using index)来返回记录,直接从索引中过滤不需要的记录并返回命中的结果。这是在MySQL服务器层,但无须再回表查询记录
- 从数据表中返回数据,然后过滤不满足条件的记录(Using where)。这是在服务器层完成,MySQL需要先从数据表读出记录然后过滤
怎么优化查询?
-
可以将一个复杂查询改成多个简单查询
因为MySQL从设计上让连接和断开都非常轻量级,加上现代网络速度非常快,多个小查询不成问题
-
切分查询
将一个大查询,切分成小查询,每个小查询的功能完全一样,每次只返回小部分查询结果。比如删除旧数据,如果用一个很大的语句一次性删除大量数据,会锁住很多数据、占满事务日志、耗尽系统资源、阻塞很多重要的小查询,可以切分成每次删除1000条,循环删除
-
分解关联查询
- 让缓存效率更高
- 减少锁的竞争
- 更容易拆分数据库,方便扩展
- 提升查询效率,比如用IN()代替关联查询
- 减少冗余记录的查询
一条查询是如何执行的?
1、客户端发送一条查询给服务器
MySQL客户端和服务器之间的通信协议是“半双工”的,同一时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器端发送数据,这两个动作不能同时发生
2、服务器先检查查询缓存,如果命中缓存,则立即返回储存在缓存中的结果。否则进入下一阶段
查询的状态
Sleep:线程正在等待客户端发送新的请求
Query:线程正在查询或者正在将结果发送给客户端
Locked:服务器层等待标锁
Analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划
Copying to tmp table [on disk]:将结果拷贝到一个临时表,要么在做GROUP BY,要么文件排序、UNION表。如果后面还有“on disk”,表示MySQL正在将一个内存临时表放到磁盘上操作
Sorting result:线程正在对结果集进行排序
Sending data:线程正在传送数据
查询缓存
解析SQL语句之前,优先检查查询缓存,这是通过哈希查找实现的,如果命中缓存,那么在返回数据之前,还要检查一次用户权限
3、服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划
MySQL通过关键字对SQL语句进行解析,生成“解析树”,验证是否关键字错误、语法错误、括号是否匹配,然后验证权限
4、MySQL根据优化器生成的执行计划,调用储存引擎的API来执行查询
5、将结果返回给客户端
查询优化器
一条查询可以有很多种执行方式,最后都返回相同的结果,优化器的作用就算找到这其中最好的执行计划
-
导致优化器选择错误的执行计划的原因
-
统计信息不准确
MySQL依赖储存引擎提供的统计信息来评估成本,但是储存引擎提供的统计信息不一定准确,比如InnoDB因为有MVCC的架构,并不能维护一个数据表的行数精确统计信息
-
执行计划中的成本估算不等同与实际的执行成本
MySQL层面并不知道那些页面在内存中,哪些在磁盘上,所以查询实际执行过程中到底需要多少次物理IO是无法得知的
MySQL并不考虑其他并发执行的查询
MySQL不会考虑不受其控制的操作的成本
-
-
MySQL能够处理的优化类型
重新定义关联表的顺序
将外连接转化成内连接
-
使用等价变换规则
比如 5=5 AND a > 5 会改写成 a > 5
-
优化COUNT()、MIN()和MAX()
比如查找最小值,如果列在B-Tree中,那只要把最左的值拿出来就可以了;最大值把最右值拿出来就可以了
-
预估并转化为常数表达式
比如表达式转化为常数,有时候甚至一个查询也能转化为一个常数
覆盖索引扫描
子查询优化
-
提前终止查询
发现已经满足查询需求的时候,MySQL总是能够立刻终止查询。比如LIMIT
-
等值传播
如果两个列的值通过等式关联,那么MySQL能够把其中一个列的WHERE条件传递到另一个上。
-
列表IN()的比较
在很多数据系统中,IN()完全等同于多个OR条件的子句,因为这两者完全等价,但是在MySQL中不是这样的,MySQL将IN()列表中的数据先进行排序然后通过二分查找的方式确定列表中的值是否满足条件,这是一个O(logn)复杂度的查询,等级转换成OR是O(n),所以如果IN()列表中有大量取值的时候,MySQL的处理速度回更快
MySQL如何执行关联查询
按照where条件遍历第一个表,然后循环查询第二个表,使用on的筛选;
on的条件最好要是索引
返回结果给客户端
一旦服务器处理完最后一个关联表,开始生成第一条结果时,MySQL就可以开始向客户端逐步返回结果集了;比如Java的rs.next(),一条一条取,避免服务器内存压力过大;