工作中,我们经常会写一些很复杂的SQL,有可能是统计报表,有可能是复杂查询,当我们费尽手段把一个可以工作的SQL写出来之后,并不意味着结束了,如果这个SQL查询或者统计速度很慢,将是一个十分耗费资源的动作。即使它能工作,也有可能在执行半分钟或者更久之后能够给出结果,但执行时间越久,耗费资源就越多,越容易阻塞后续的数据库链接请求,如果这样的SQL很多,那么最终也将拖垮服务器。

EXPALIN指令

SQL为什么会执行的慢?该如何判断这个SQL的查询数量级?我们可以用 “EXPLAIN”指令,解释你的SQL。
EXPLAIN指令是一个优化神器,虽然它并不能直接告诉你该如何优化这个SQL,但是你可以根据它对你SQL的模拟执行得到你的SQL有没有用上索引,有没有做全表扫描,查询出结果的数量级是多少。这是一个基于开销的优化器,我们可以获得更多被优化器考虑到的访问策略的细节,以及当运行SQL语句时,哪种策略预计会被采用。

  1. mysql> explain select * from servers;
  2. +----+-------------+---------+------+---------------+------+---------+------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+---------+------+---------------+------+---------+------+------+-------+
  5. | 1 | SIMPLE | servers | ALL | NULL | NULL | NULL | NULL | 1 | NULL |
  6. +----+-------------+---------+------+---------------+------+---------+------+------+-------+
  7. row in set (0.03 sec)

我们做了一个简单的例子,我们可以看到explain的用法就是 “explain 目标SQL”,得出的结果有10列,如上代码块,我们会挨个解释这些列的意义。

1、id

这个id并不是表中记录的id,而是查询语句或者操作表的顺序,包含几种情况
id相同时,执行顺序由上到下
id不同,如果是子查询,id序号会递增,id越大优先级越高,越先被执行
id既有相同的,也有不同的。id相同的被认为是一组,顺序从上到下;在所有组中,id越大优先级越高。

2、select type

表示查询中每个select字句的类型
(1)simple:简单select,不使用union或子查询等
(2)primary:查询中若包含任何复杂的子部分,最外层的select会被标记为primary
(3)union:union中的第二个或者后边的select语句
(4)subquery:在select或者where中包含了子查询
(5)derived:在from列表中包含的子查询被标记为derived(衍生表),mysql会递归这些子查询并放到临时表中
(6)union result:从union表中获取select查询的结果

3、table

标识这一行的数据是关于哪一张表的,有的时候并不是真实的表名,比如是derived衍生表

  1. mysql> explain select * from (select * from ( select * from t1 where id=2602) a) b;
  2. +----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
  5. | 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
  6. | 2 | DERIVED | <derived3> | system | NULL | NULL | NULL | NULL | 1 | |
  7. | 3 | DERIVED | t1 | const | PRIMARY,idx_t1_id | PRIMARY | 4 | | 1 | |
  8. +----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+

4、type

表示在表中找到所需行的方式,又叫做访问类型。常用的类型由:all,index,range,ref,eq_ref,const,system,null(性能从差到好)
all::全表扫描
index:index和all的区别是index只遍历索引树
range:只检索给定范围的行记录,使用一个索引来选择行
ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref:跟ref类似,区别在于使用的索引是唯一索引
sonst,system:当SQL对查询部分进行优化,并转换为一个常量时,使用这些类型访问。
null:在优化过程中分解语句,执行时甚至不用访问表或者索引。例如从一个索引列中选取最小值可以单独通过索引查找完成。

5、possible_keys

指出能使用哪个索引在表中找到记录,查询涉及的字段若存在索引,会在这一列列出来,但不一定会用到该索引。
如果该列是null,就说明没有用到任何索引,这时候你可能需要观察表中的查询高频列增加索引,然后在explain看下结果

6、keys

显示实际决定使用的索引列
如果没有选择到索引,则是null。如果要强制使用某个索引,需要在查询中加语句:force index,use index。如果不想使用某索引,可以使用 ignore index

7、key_len

表示索引中使用的字节数,可以通过该列计算查询中使用的索引长度

8、ref

表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。

9、rows

标识此次查询根据表统计信息及索引选用情况,估算到的找到符合条件的记录所需要读取的行数

10、extra

包含此次查询的详细信息:
using filesort:表明此次查询会使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。查询中无法利用索引完成的排序被称为文件排序
using temporary:此次查询使用到了临时表保存中间结果,常见于order by和group by
using index:表明此次查询实用到了覆盖索引,避免了全表扫描
using where:表明此次查询where的条件里边,有索引值存在,使用到了索引去查询
using join buffer:使用了链接缓存
impossible where:where条件里边总是false,不能用来获取任何记录
select tables optimized away:这意味着仅通过使用索引,优化器可能仅从聚合函数中返回一行

explain指令,不会考虑各种cache,也不会告诉你它做了什么优化,也只能支持select操作,部分的统计信息是估算的,并非精确值。
我们使用explain的时候,在大部分情况下都是要关注:possible_keys,Key,rows,Extra这四项,关注此次查询是否用到了索引,大约查询的数据在什么量级,额外用到了哪些东西,这是我们着重优化的点。