分析查询语句:EXPLAIN
定位了查询慢的SQL之后,我们就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句。DESCRIBE 语句的使用方法与EXPLAIN语句是一样的,并且 分析结果也是一样的。
MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Quy提供它认为最优的执行计划
(他认为最优的数据检索方式,但不见得是DB认为是最优的,这部分最耗费时间)
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。MySQL 为我们提供了 EXPLAIN
语句来帮助我们查看某个查询语句的具体执行计划,大家看懂 EXPLAIN
语句的各个输出项,可以有针对性的提升我们查询语句的性能。
基本语法
EXPLAIN 或 DESCRIBE 语句的语法形式如下:
EXPLAIN SELECT select_options;
或者
DESCRIBE SELECT select_options;
如果我们想看看某个查询的执行计划的话,可以在具体的查询语句前边加一个 EXPLAIN
:
mysql> explain select 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
EXPLAIN
语句输出的各个列的作用如下:
列名 | 描述 |
---|---|
id | 在一个大的查询语句中每个SELECT关键字都对应一个 唯一的id id 如果相同,可以认为是一组,从上往下顺序执行; 在所有组中,id 值越大,优先级越高,越先执行 |
select_type | SELECT关键字对应的那个查询的类型 |
table | 表名 |
partitions | 匹配的分区信息,非分区表,该项为 NULL。一般情况下我们的查询语句的执行计划的 partitions 列的值都是 NULL。 |
type | 支队单表的访问方法 |
possible_type | 可能用到的索引 |
key | 实际上使用的索引 |
key_len | 实际使用到的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数,值越小越好 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra | 一些额外的信息 |
重要字段解析
select_type
一条大的查询语句里边可以包含若干个 SELECT 关键字,每个 SELECT 关键字代表着一个小的查询语句
,而每个 SELECT:关键字的 FROM 子句中都可以包含若干张表(这些表用来做连接查询),每一张表都对应着执行计划输出中的一条记录
,对于在同一个 SELECT 关键字中的表来说,它们的 id 值是相同的。
MySQL 为每一个 SELECT 关键字代表的小查询都定义了一个称之为 select_type 的属性,意思是我们只要知道了某个小查询的 select_type 属性
,就知道了这个小查询在整个大查询中扮演了一个什么角色
,我们看一下 select_type 都能取哪些值,请看官方文档:
名称 | 描述 |
---|---|
SIMPLE | Simple SELECT(not using UNION or subqueries) 简单 SELECT(不使用 UNION 或子查询) |
PRIMAPY | Outermost SELECT 最外层选择 |
UNION | Second or later SELECT statement in a UNION UNION 中的第二个或以后的 SELECT 语句 |
UNION RESULT | Result of a UNION UNION 的结果 |
SUBQUERY | First SELECT in subquery 子查询中的第一个 SELECT |
DEPENDENT SUBQUERY | First SELECT in subquery,dependent on outer query 子查询中的第一个 SELECT,依赖于外部查询 |
DERIVED | Derived table 派生表 |
MATERIALIZED | Materialized subquery 物化子查询 |
UNCACHEABLE SUBQUERY | A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query 一个子查询,其结果无法缓存,必须为外部查询的每一行重新计算 |
UNCACHEABLE UNION | The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY) 属于不可缓存子查询的 UNION 中的第二个或更晚选择(请参阅 UNCACHEABLE SUBQUERY) |
type
执行计划的一条记录就代表着 MySQL 对某个表的执行查询时的访问方法
,又称“访问类型",其中的 type 列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到 type 列的值是ref,表明 MySQL 即将使用 ref 访问方法来执行对 s1
表的查询。
完整的访问方法如下:system
,const
,eq_ref
,ref
,fulltext
,ref_or_null
,index_merge
, unique_subquery
,index_subquery
,range
,index
,ALL
;
-
system
当表中
只有一条记录
并且该表使用的存储引擎的统计数据是精确的,比如 MyISAM,Memory。那么对该表的访问方法就是system
。 -
const
当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是
cost
;示例是根据 employees 表中的主键进行查询:
-
eq_ref
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是
eq_ref
。 -
ref
当通过普通的二级索引列与常量进行等值匹配来查询某个表时,那么对该表的访问方法就可能是
ref
-
ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也可以是 NULL 值时,那么对该表的访问方法就可能是
ref_or_null
; -
index_merge
单表访问方法时在某些场景下可以使用
Intersection
、Union
、sort-Union
这三种索引合并的方式米执行查询 -
unique_subquery
unique_subquery 是针对在一些包含
IN
子查询的查询语句中,如果查询优化器决定将IN
子查询转换为EXISTS
子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery
。 -
range
如果使用索引获取某些~范围区间`的记录,那么就可能使用到`range`访问方法
-
index
当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是
index
-
ALL
全表扫描
possible_key 与 key
可供选择的索引与实际用到的索引
在 EXPLAIN 语句输出的执行计划中,possible_keys 列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key 列表示实际用到的索引有哪些,如果为 NULL 则没有使用索引。
key_len
实际使用到的索引长度(即:字节数),检查是否充分的利用上索引
。对于联合索引来说:值越大越好
。说明充分利用到了索引。
计算规则示例:
- varchar(10):变长字段且允许 NULL = 10 * (character set:utf8=3,gbk=2,latinl=1) + 1 (NULL) + 2 (变长字段)
- varchar(10):变长字段且不允许 NULL = 10 * (character set:utf8=3,gbk=2,latinl=1 ) + 2 ( 变长字段 )
- char(10):固定字段且允许NULL = 10 (character set:utf8=3,gbk=2,latinl=1) + 1(NULL)
- char(10):固定字段且不允许NULL = 10 (character set:utf8=3,gbk=2,latinl=1)
filtered
某个表经过搜索条件过滤后剩余记录条数的百分比。
如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。
对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即:rows * filtered)
Extra
Extra
列是用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句
。MySQL提供的额外信息有好几十个,只挑比较重要的额外信息介绍。
-
No tables used
当查询语句没有 FROM 子句时会提示该额外信息,比如:
-
Impossible WHERE
查询语句的
WHERE
子句水远为FALSE
时将会提示该额外信息: -
Using where
当我们使用全表扫描来执行对某个表的查询,并且该语句的
WHERE
子句中有针对该表的搜索条件时,在Extra
列中会提示上述额外信息。当使用索引访问米执行对某个表的查询,并且该语句的
WHERE
子句中有除了该索引包含的列之外的其他搜索条件时,在Extra列中也会提示上述额外信息。 -
No matching min/max row
当查询列表处有
MIN
或者MAX
聚合函数,但是并没有符合WHERE
子句中的搜索条件的记录时,将会提示该额外信息。 -
Using index
当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可使用覆盖索引的情况下,在
Extra
列将会提示该额外信息。比如说下边这个查询中只需要用到索引而不需要回表操作: -
Using index condition
搜索条件里面出现了索引列,但是却不能使用索引:
-
join buffer
在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫
join buffer
内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法
: -
Not exists
当我们使用左(外)连接时,如果
WHEE
子句中包含要求被驱动表的某个列等于NULL
值的搜索条件,而且那个列又是不允许存储NULL
值的,那么在该表的执行计划的Extra列就会提示Not exists
额外信息: