第 15 章 查询优化的百科全书——EXPLAIN 详解

31 阅读8分钟

一条查询语句在经过 MySQL 查询优化器的各种基于成本和规则的优化后会生成一个所谓的执行计划,它展示了接下来具体执行查询的方式。

使用Explain语句来查看执行计划。

列名描述
id在一个大的查询语句中每个 SELECT 关键字都对应一个唯一的 id
select_typeSELECT 关键字对应的查询类型
table表名
partitions匹配的分区信息
type针对单表的访问方法
possible_keys可能用到的索引
key实际上使用的索引
key_len实际使用到的索引长度
ref当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows预估的需要读取的记录条数
filtered某个表经过搜索条件过滤后剩余记录条数的百分比
Extra一些额外的信息

15.1 执行计划输出中各列详解

15.1.1 table

EXPLAIN 语句输出的每条记录都对应着某个单表的访问方法,该条记录的 table 列代表着该表的表名。

 EXPLAIN SELECT * FROM s1;

在这里插入图片描述

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

在这里插入图片描述

15.1.2 id

查询语句中每出现一个SELECT关键字,MySQL 就会为它分配一个唯一的 id。

在连接查询的执行计划中,每个表都会对应一条记录,这些记录的 id 是相同的,出现在前边的表是驱动表,后面表的是被驱动表。

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';

在这里插入图片描述

查询优化器可能对涉及子查询的查询语句进行重写,从而转换为连接查询。

对于包含 UNION 子句的查询语句,id 可能为 NULL

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

UNION 去重时内部创建了一个名为 <union1,2> 的临时表,它的 id 为 NULL。

UNION 因为不需要去重,所以没有这个问题。

15.1.3 select_type

名称描述举例
SIMPLE查询语句中不包含 UNION 或者子查询EXPLAIN SELECT * FROM s1
PRIMARY包含 UNION、UNION ALL 或者子查询的查询,其中最左边的那个查询。EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
UNION包含 UNION、UNION ALL 或者子查询的查询,除最左边的那个以外的查询。同上
UNION RESULTMySQL 使用临时表来完成 UNION 的去重工作,该临时表的查询。同上
SUBQUERY不相关子查询,不能转为 semi-join,使用物化方案的子查询。EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
DEPENDENT SUBQUERY相关子查询,不能转为 semi-join 的子查询。该类型的查询可能被会执行多次EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
DEPENDENT UNION在包含 UNION 或者 UNION ALL 的大查询中,如果各个小查询都依赖于外层查询的话,除了最左边的那个小查询的查询。EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
DERIVED采用物化方式执行的,包含派生表的查询。EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS derived_s1 where c > 1;
MATERIALIZED包含子查询,并将子查询物化之后与外层查询进行连接的查询。EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
UNCACHEABLE SUBQUERY不常用,略
UNCACHEABLE UNION不常用,略

15.1.4 partitions

分区,略

15.1.5 type

type描述举例
system当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如 MyISAM、Memory 时CREATE TABLE t(i int) Engine=MyISAM;
INSERT INTO t VALUES(1);
EXPLAIN SELECT * FROM t;
const根据主键或唯一二级索引列与常数进行等值匹配时EXPLAIN SELECT * FROM s1 WHERE id = 5;
eq_ref连接查询时,被驱动表是通过主键或者唯一二级索引列进行等值匹配时EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
ref使用普通二级索引与常量进行等值匹配时EXPLAIN SELECT * FROM s1 WHERE key1 = '1';
fulltext全文索引,略
ref_or_null使用普通二级索引进行等值匹配,但索引列的值可以是 NULL 时EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
index_merge发生索引合并时EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
unique_subquery将 IN 转换为 EXISTS 并且子查询可以使用主键进行等值匹配时EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
index_subquery与 unique_subquery 类似,只不过使用的是普通索引EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key3 FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
range使用索引获取某些区间范围的记录时EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
index使用索引覆盖,但需要扫描全部的索引记录时EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
ALL全表扫描EXPLAIN SELECT * FROM s1

15.1.6 possible_keys 和 key

可能用到的索引和实际用到的索引。

15.1.7 key_len

表示使用某个索引执行查询时该索引记录的最大长度,它由三个部分构成:

  1. 对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值。比如字符集utf8,类型VARCHAR(100),则占用 100 × 3 = 300 字节
  2. 如果该索引列可以存储 NULL 值,则 key_len 比不可以存储 NULL 值时多 1 个字节
  3. 对于变长字段,都会有 2 个字节的存储该变长列的实际长度

key_len 的主要作用是为了让我们区分某个使用联合索引的查询具体用了几个索引列

EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';

在这里插入图片描述

EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';

在这里插入图片描述

15.1.8 ref

当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是 const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery 之一时,ref 展示的就是与索引列作等值匹配的是什么。

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

在这里插入图片描述

15.1.9 rows

预计扫描的记录行数。

15.1.10 filtered

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';

在这里插入图片描述

从执行计划可以看出,查询优化器打算把 s1 当作驱动表, s2 当作被驱动表。驱动表的 rows 为 10141,filtered 列为 10.00,意味着驱动表的扇出值就是 10141 × 10.00% = 1014.1,说明还要对被驱动表执行大约 1014 次查询。

15.1.11 Extra

名称含义举例
No tables used当没有 FROM 子句时EXPLAIN SELECT 1;
Impossible WHERE当 WHERE 子句永远为 FALSE 时EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
No matching min/max row当查询列表有 MIN 或 MAX 聚集函数,但是并没有符合 WHERE 子句条件的记录时EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
Using index当可以使用索引覆盖时而不用回表时EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a';
Using index condition当有些搜索条件中虽然出现了索引列,但却不能使用索引而将要进行索引下推时EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
Using where使用全表扫描并且 WHERE 子句中有针对该表的条件或使用索引访问但 WHERE 子句中有除了该索引列以外的查询条件时EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
Using join buffer(Block Nested Loop)连接查询时,使用基于块的嵌套循环算法EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
Not exists使用左外连接,如果 WHERE 子句中包含被驱动表中某个列等于 NULL 的条件,而那个列又不允许为 NULL 时EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
Using intersect/union/sort union出现索引合并时EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND key3 = 'a';
Zero limit当 LIMIT 子句的参数为 0 时EXPLAIN SELECT * FROM s1 LIMIT 0;
Using filesort在内存或磁盘上进行排序时EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;
Using temporary当使用临时表时,如 DISTINCT、GROUP BY、UNION 等EXPLAIN SELECT DISTINCT common_field FROM s1;
Start temporary, End temporary当 IN 子查询转换成 semi-join,并且使用 DuplicateWeedout 执行策略时EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a');
LooseScan当 IN 子查询转换成 semi-join,并且使用 LooseScan执行策略时EXPLAIN SELECT * FROM s1 WHERE key3 IN (SELECT key1 FROM s2 WHERE key1 >'z');
FirstMatch(tbl_name)当 IN 子查询转换成 semi-join,并且使用 FirstMatch(tbl_name)执行策略时EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key1 FROM s2 where s1.key3 = s2.key3);

索引条件下推:

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';

其中key1 > 'z'可以使用索引,但是key1 LIKE '%a'却无法使用索引。先根据前者定位到具体记录,回表前先检测下该记录是否满足后者,满足才回表。这种方式可以大大减少回表的次数,称为索引条件下推(Index Condition Pushdown)

15.2 Json 格式的执行计划

EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a';

15.3 Extented EXPLAIN

使用 EXPLAIN 查看执行计划后,还可以使用 SHOW WARNINGS 来查看这个查询计划的一些扩展信息。

EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;

SHOW WARNINGS;

15.4 总结

  1. 通过 EXPLAIN 语句可以查看某个语句的执行计划