先看看具体有哪些字段:html
mysql> EXPLAIN SELECT 1;

其实除了以SELECT开头的查询语句,其他的DELETE、INSERT、REPLACE以及UPDATE语句前边均可以加上EXPLAIN这个词儿,用来查看这些语句的执行计划mysql
建两张测试表:web
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
key1 VARCHAR(100),
key2 VARCHAR(100),
key3 VARCHAR(100),
name VARCHAR(100),
PRIMARY KEY (id),
KEY idx_key1 (key1),
KEY idx_key2_key3(key2, key3)
) Engine=InnoDB CHARSET=utf8;
CREATE TABLE t2 (
id INT NOT NULL AUTO_INCREMENT,
key1 VARCHAR(100),
key2 VARCHAR(100),
key3 VARCHAR(100),
name VARCHAR(100),
PRIMARY KEY (id),
KEY idx_key1 (key1),
KEY idx_key2_key3(key2, key3)
) Engine=InnoDB CHARSET=utf8;
两个变种
explain extended
会在 explain 的基础上额外提供一些查询优化的信息。紧随其后经过 show warnings 命令能够 获得优化后的查询语句,从而看出优化器优化了什么算法
explain extended SELECT * FROM t1 where key1 = '11';
show warnings;
explain partitions
相比 explain 多了个 partitions 字段,若是查询是基于分区表的话,会显示查询将访问的分区。sql
EXPLAIN PARTITIONS SELECT * FROM t1 INNER JOIN t2 ON t1.key3 = t2.key3;

table列
这一列表示 explain 的一行正在访问哪一个表数据库
mysql> EXPLAIN SELECT * FROM t1;

这个查询语句只涉及对t1表的单表查询,因此EXPLAIN输出中只有一条记录,其中的table列的值是t1,代表这条记录是用来讲明对t1表的单表访问。微信
mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2;

能够看到这个链接查询的执行计划中有两条记录,这两条记录的table列分别是t1和t2,这两条记录用来分别说明对t1表和t2表的访问编辑器
注意:函数
当 from 子句中有子查询时,table列是
<derivenN>
格式,表示当前查询依赖 id=N 的查询,因而先执行 id=N 的查询。当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select 行id。oop
id列
id列的编号是 select 的序列号,有几个 select 就有几个id,而且id的顺序是按 select 出现的顺序增加的。
id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行
好比下边这个查询中只有一个SELECT关键字,因此EXPLAIN的结果中也就只有一条id列为1的记录:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 = 'e038f672a8';

对于链接查询来讲,一个SELECT关键字后边的FROM子句中能够跟随多个表,因此在链接查询的执行计划中,每一个表都会对应一条记录,可是这些记录的id值都是相同的,好比:
mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2;

能够看到,上述链接查询中参与链接的t1和t2表分别对应一条记录,可是这两条记录对应的id值都是1。
注意:
在链接查询的执行计划中,每一个表都会对应一条记录,这些记录的id列的值是相同的,出如今前边的表表示驱动表,出如今后边的表表示被驱动表。因此从上边的EXPLAIN输出中咱们能够看出,查询优化器准备让t2表做为驱动表,让t1表做为被驱动表来执行查询
对于包含子查询的查询语句来讲,就可能涉及多个SELECT关键字,因此在包含子查询的查询语句的执行计划中,每一个SELECT关键字都会对应一个惟一的id值,好比这样:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2) OR key3 = 'a1b6cee57a';

从输出结果中咱们能够看到,t1表在外层查询中,外层查询有一个独立的SELECT关键字,因此第一条记录的id值就是1,t2表在子查询中,子查询有一个独立的SELECT关键字,因此第二条记录的id值就是2。
可是这里你们须要特别注意,查询优化器可能对涉及子查询的查询语句进行重写,从而转换为链接查询。因此若是咱们想知道查询优化器对某个包含子查询的语句是否进行了重写,直接查看执行计划就行了,好比说:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN (SELECT key3 FROM t2 WHERE t1.key1 = 'a1b6cee57a');

能够看到,虽然咱们的查询语句是一个子查询,可是执行计划中t1和t2表对应的记录的id值所有是1,这就代表了查询优化器将子查询转换为了链接查询。
对于包含UNION子句的查询语句来讲,每一个SELECT关键字对应一个id值也是没错的,不过仍是有点儿特别的东西,比方说下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2;

UNION子句是为了把id为1的查询和id为2的查询的结果集合并起来并去重,因此在内部建立了一个名为<union1, 2>
的临时表(就是执行计划第三条记录的table列的名称),id为NULL代表这个临时表是为了合并两个查询的结果集而建立的。
跟UNION对比起来,UNION ALL就不须要为最终的结果集进行去重,它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户,因此也就不须要使用临时表。因此在包含UNION ALL子句的查询的执行计划中,就没有那个id为NULL的记录,以下所示:
mysql> EXPLAIN SELECT * FROM t1 UNION ALL SELECT * FROM t2;

select_type列
MySQL每个SELECT关键字表明的小查询都定义了一个称之为select_type
的属性,意思是咱们只要知道了某个小查询的select_type
属性,就知道了这个小查询在整个大查询中扮演了一个什么角色
下面是官方文档介绍:
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain_select_type

SIMPLE
查询语句中不包含UNION或者子查询的查询都算做是SIMPLE类型,比方说下边这个单表查询的select_type
的值就是SIMPLE:
mysql> EXPLAIN SELECT * FROM t1;

PRIMARY
对于包含UNION、UNION ALL或者子查询的大查询来讲,它是由几个小查询组成的,其中最左边的那个查询的select_type
值就是PRIMARY,比方说:
mysql> EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2;

从结果中能够看到,最左边的小查询SELECT * FROM t1
对应的是执行计划中的第一条记录,它的select_type
值就是PRIMARY。
UNION
对于包含UNION或者UNION ALL的大查询来讲,它是由几个小查询组成的,其中除了最左边的那个小查询之外,其他的小查询的select_type
值就是UNION,能够对比上一个例子的效果
UNION RESULT
MySQL选择使用临时表来完成UNION查询的去重工做,针对该临时表的查询的select_type
就是UNION RESULT,一样对比上面的例子
SUBQUERY
若是包含子查询的查询语句不可以转为对应的semi-join的形式,而且该子查询是不相关子查询,而且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个SELECT关键字表明的那个查询的select_type
就是SUBQUERY,好比下边这个查询:
概念解释:
semi-join子查询,是指当一张表在另外一张表找到匹配的记录以后,半链接(semi-jion)返回第一张表中的记录。与条件链接相反,即便在右节点中找到几条匹配的记录,左节点 的表也只会返回一条记录。另外,右节点的表一条记录也不会返回。半链接一般使用IN 或 EXISTS 做为链接条件
物化:这个将子查询结果集中的记录保存到临时表的过程称之为物化(Materialize)。那个存储子查询结果集的临时表称之为物化表。正由于物化表中的记录都创建了索引(基于内存的物化表有哈希索引,基于磁盘的有B+树索引),经过索引执行IN语句判断某个操做数在不在子查询结果集中变得很是快,从而提高了子查询语句的性能。
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2) OR key3 = 'a1b6cee57a';

能够看到,外层查询的select_type
就是PRIMARY,子查询的select_type
就是SUBQUERY。
DEPENDENT SUBQUERY
若是包含子查询的查询语句不可以转为对应的semi-join的形式,而且该子查询是相关子查询,则该子查询的第一个SELECT关键字表明的那个查询的select_type
就是DEPENDENT SUBQUERY,好比下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2 WHERE t1.key2 = t2.key2) OR key3 = 'a1b6cee57a';

DEPENDENT UNION
在包含UNION或者UNION ALL的大查询中,若是各个小查询都依赖于外层查询的话,那除了最左边的那个小查询以外,其他的小查询的select_type
的值就是DEPENDENT UNION。比方说下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2 WHERE key1 = 'a1b6cee57a' UNION SELECT key1 FROM t1 WHERE key1 = 'a1b6cee57a');

这个查询比较复杂啊,大查询里包含了一个子查询,子查询里又是由UNION连起来的两个小查询。从执行计划中能够看出来,SELECT key1 FROM t2 WHERE key1 = 'a1b6cee57a'
这个小查询因为是子查询中第一个查询,因此它的select_type
是DEPENDENT SUBQUERY,而SELECT key1 FROM t1 WHERE key1 = 'a1b6cee57a'
这个查询的select_type
就是DEPENDENT UNION。
DERIVED
对于采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的select_type
就是DERIVED,比方说下边这个查询:
mysql> EXPLAIN SELECT * FROM (SELECT key1, count(*) as t FROM t1 GROUP BY key1) AS derived_t1 where t > 1;

从执行计划中能够看出,id为2的记录就表明子查询的执行方式,它的select_type
是DERIVED,说明该子查询是以物化的方式执行的。id为1的记录表明外层查询,你们注意看它的table列显示的是<derived2>
,表示该查询是针对将派生表物化以后的表进行查询的。
MATERIALIZED
当查询优化器在执行包含子查询的语句时,选择将子查询物化以后与外层查询进行链接查询时,该子查询对应的select_type
属性就是MATERIALIZED,好比下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2);

执行计划的第三条记录的id值为2,说明该条记录对应的是一个单表查询,从它的select_type
值为MATERIALIZED能够看出,查询优化器是要把子查询先转换成物化表。而后看执行计划的前两条记录的id值都为1,说明这两条记录对应的表进行链接查询,须要注意的是第二条记录的table列的值是<subquery2>
,说明该表其实就是id为2对应的子查询执行以后产生的物化表,而后将s1和该物化表进行链接查询。
type列
这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
通常来讲,得保证查询达到range级别,最好达到ref
NULL
mysql可以在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,能够单独查找索引来完成,不须要在执行时访问表
mysql> explain select min(id) from t1;

eq_ref
primary key 或 unique key 索引的全部部分被链接使用 ,最多只会返回一条符合条件的记录。这多是在 const 以外最好的联接类型了,简单的 select 查询不会出现这种 type。
在链接查询时,若是被驱动表是经过主键或者惟一二级索引列等值匹配的方式进行访问的(若是该主键或者惟一二级索引是联合索引的话,全部的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
,比方说:
mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id;

从执行计划的结果中能够看出,MySQL打算将t2做为驱动表,t1做为被驱动表,重点关注t1的访问方法是eq_ref
,代表在访问t1表的时候能够经过主键的等值匹配来进行访问。
ref
当经过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就多是ref
相比 eq_ref
,不使用惟一索引,而是使用普通索引或者惟一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 = 'a';

能够看到type列的值是ref,代表MySQL即将使用ref访问方法来执行对t1表的查询
system,const
mysql能对查询的某部分进行优化并将其转化成一个常量(能够看show warnings 的结果)。用于 primary key 或 unique key 的全部列与常数比较时,因此表最多有一个匹配行,读取1次,速度比较快。system是const的特例,表里只有一条元组匹配时为system
mysql> EXPLAIN SELECT * FROM t1 WHERE id = 5;

ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也能够是NULL值时,那么对该表的访问方法就多是ref_or_null
,好比说:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 = 'a' OR key1 IS NULL;

index_merge
通常状况下对于某个表的查询只能使用到一个索引,但在某些场景下可使用多种索引合并的方式来执行查询,咱们看一下执行计划中是怎么体现MySQL使用索引合并的方式来对某个表执行查询的:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 = 'a' OR key2 = 'a';

从执行计划的type列的值是index_merge
就能够看出,MySQL打算使用索引合并的方式来执行对t1表的查询。
unique_subquery
相似于两表链接中被驱动表的eq_ref
访问方法,unique_subquery
是针对在一些包含IN子查询的查询语句中,若是查询优化器决定将IN子查询转换为EXISTS子查询,并且子查询可使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery
,好比下边的这个查询语句:
mysql> EXPLAIN SELECT * FROM t1 WHERE key2 IN (SELECT id FROM t2 where t1.key1 = t2.key1) OR key3 = 'a';

能够看到执行计划的第二条记录的type值就是unique_subquery
,说明在执行子查询时会使用到id列的索引。
range
范围扫描一般出如今 in(), between ,> ,<, >= 等操做中。使用一个索引来检索给定范围的行。
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 IN ('a', 'b', 'c');

index
当咱们可使用索引覆盖,但须要扫描所有的索引记录时,该表的访问方法就是index
扫描全表索引,这一般比ALL快一些。(index是从索引中读取的,而all是从硬盘中读取)
ALL
最熟悉的全表扫描
mysql> explain select * from t2;
通常来讲,这些访问方法按照咱们介绍它们的顺序性能依次变差。其中除了All这个访问方法外,其他的访问方法都能用到索引,除了index_merge
访问方法外,其他的访问方法都最多只能用到一个索引。
possible_keys和key列
possible_keys
列显示查询可能使用哪些索引来查找。
explain 时可能出现 possible_keys
有列,而 key 显示 NULL 的状况,这种状况是由于表中数据很少,mysql认为索引对此查询帮助不大,选择了全表查询。
若是possible_keys
列是NULL,则没有相关的索引。在这种状况下,能够经过检查 where 子句看是否能够创造一个适当的索引来提升查询性能,而后用 explain 查看效果。
key列显示mysql实际采用哪一个索引来优化对该表的访问。若是没有使用索引,则该列是 NULL。若是想强制mysql使用或忽视possible_keys
列中的索引,在查询中使用 force index、ignore index。
比方说下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 > 'z' AND key2 = 'a';

上述执行计划的possible_keys
列的值是idx_key1,idx_key2_key3
,表示该查询可能使用到idx_key1,idx_key2_key3
两个索引,而后key列的值是idx_key3
,表示通过查询优化器计算使用不一样索引的成本后,最后决定使用idx_key3
来执行查询比较划算。
须要注意的一点是,possible_keys
列中的值并非越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间,因此若是能够的话,尽可能删除那些用不到的索引。
key_len列
这一列显示了mysql在索引里使用的字节数,经过这个值能够算出具体使用了索引中的哪些列
对于使用固定长度类型的索引列来讲,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来讲,好比某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。
若是该索引列能够存储NULL值,则key_len
比不能够存储NULL值时多1个字节。
对于变长字段来讲,都会有2个字节的空间来存储该变长列的实际长度。
当字符串过长时,mysql会作一个相似左前缀索引的处理,将前半部分的字符提取出来作索引。
key_len计算规则以下:字符串 char(n):n字节长度 varchar(n):2字节存储字符串长度,若是是utf-8,则长度 3n + 2 数值类型 tinyint:1字节 smallint:2字节 int:4字节 bigint:8字节 时间类型 date:3字节 timestamp:4字节 datetime:8字节
好比下边这个查询:
mysql> EXPLAIN SELECT * FROM s1 WHERE id = 5;

因为id列的类型是INT,而且不能够存储NULL值,因此在使用该列的索引时key_len
大小就是4。
对于可变长度的索引列来讲,好比下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 = 'a';

因为key1列的类型是VARCHAR(100)
,因此该列实际最多占用的存储空间就是300字节,又由于该列容许存储NULL值,因此key_len
须要加1,又由于该列是可变长度列,因此key_len
须要加2,因此最后ken_len
的值就是303。
rows列
这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。
若是查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows列就表明预计须要扫描的行数,若是使用索引来执行查询时,执行计划的rows列就表明预计扫描的索引记录行数。好比下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 > 'a';

咱们看到执行计划的rows列的值是113,这意味着查询优化器在通过分析使用idx_key1
进行查询的成本以后,以为知足key1 > 'a'
这个条件的记录只有113条。
ref列
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:t1.id
)
ref列展现的就是与索引列做等值匹配的值什么,好比只是一个常数或者是某个列。你们看下边这个查询:
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 = 'a';

能够看到ref列的值是const,代表在使用idx_key1
索引执行查询时,与key1列做等值匹配的对象是一个常数,固然有时候更复杂一点:
mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id;

能够看到对被驱动表t1的访问方法是eq_ref
,而对应的ref列的值是canal_manager.t2.id
,这说明在对被驱动表进行访问时会用到PRIMARY索引,也就是聚簇索引与一个列进行等值匹配的条件,于t2表的id做等值匹配的对象就是canal_manager.t2.id
列(注意这里把数据库名也写出来了)。
有的时候与索引列进行等值匹配的对象是一个函数,比方说下边这个查询
mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t2.key1 = UPPER(t1.key1);

咱们看执行计划的第二条记录,能够看到对t2表采用ref访问方法执行查询,而后在查询计划的ref列里输出的是func,说明与t2表的key1列进行等值匹配的对象是一个函数。
Extra列
顾名思义,Extra列是用来讲明一些额外信息的,咱们能够经过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。
Using index
查询的列被索引覆盖,而且where筛选条件是索引的前导列,是性能高的表现。通常是使用了覆盖索引(索引包含了全部查询的字段)。对于innodb来讲,若是是辅助索引性能会有很多提升
mysql> EXPLAIN SELECT key1 FROM t1 WHERE key1 = 'a';
Using where
当咱们使用全表扫描来执行对某个表的查询,而且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。好比下边这个查询
mysql> EXPLAIN SELECT * FROM t1 WHERE name= 'a1b6cee57a';

Using where Using index
查询的列被索引覆盖,而且where筛选条件是索引列之一可是不是索引的前导列,意味着没法直接经过索引查找来查询到符合条件的数据
mysql> EXPLAIN SELECT id FROM t1 WHERE key3= 'a1b6cee57a';

NULL
查询的列未被索引覆盖,而且where筛选条件是索引的前导列,意味着用到了索引,可是部分字段未被索引覆盖,必须经过“回表”来实现,不是纯粹地用到了索引,也不是彻底没用到索引
mysql> EXPLAIN SELECT * FROM t1 WHERE key2= 'a1b6cee57a';

Using index condition
与Using where相似,查询的列不彻底被索引覆盖,where条件中是一个前导列的范围;
mysql> EXPLAIN SELECT * FROM t1 WHERE key1 like '1';

Using temporary
在许多查询的执行过程当中,MySQL可能会借助临时表来完成一些功能,好比去重、排序之类的,好比咱们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程当中,若是不能有效利用索引来完成查询,MySQL颇有可能寻求经过创建内部的临时表来执行查询。若是查询中使用到了内部的临时表,在执行计划的Extra列将会显示Using temporary提示,比方说这样:
name没有索引,此时建立了张临时表来distinct
mysql> explain select distinct name from t1;

key1创建了idx_key1索引,此时查询时extra是using index,没有用临时表
mysql> explain select distinct key1 from t1;

Using filesort
mysql 会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。此时mysql会根据联接类型浏览全部符合条件的记录,并保存排序关键字和行指针,而后排序关键字并按顺序检索行信息。这种状况下通常也是要考虑使用索引来优化的。
name未建立索引,会浏览t1整个表,保存排序关键字name和对应的id,而后排序name并检索行记录
mysql> explain select * from t1 order by name;

key1创建了idx_key1索引,此时查询时extra是using index
mysql> explain select * from t1 order by key1;
Using join buffer (Block Nested Loop)
在链接查询执行过程当中,当被驱动表不能有效的利用索引加快访问速度,MySQL通常会为其分配一块名叫join buffer的内存块来加快查询速度,也就是咱们所讲的基于块的嵌套循环算法,好比下边这个查询语句:
mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.key3 = t2.key3;

No tables used
当查询语句的没有FROM子句时将会提示该额外信息,好比:
mysql> EXPLAIN SELECT 1;

Impossible WHERE
查询语句的WHERE子句永远为FALSE时将会提示该额外信息,比方说:
mysql> EXPLAIN SELECT * FROM t1 WHERE 1 != 1;

参考:
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-extra-information


扫码关注
本文分享自微信公众号 - 月伴飞鱼(gh_c4183eee9eb9)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。