02-MySQL执行计划详解(EXPLAIN)

转载自:http://www.ywnds.com/?p=5202

本文只做显示格式调整,和极少数笔误的校正。

一、环境准备

为了更好地看看执行计划,需要一个数据量不算小的数据库作为示例。本文选用MySQL官方文档中提供的示例数据库之一:employees。这个数据库关系复杂度适中,且数据量较大。下图是这个数据库的E-R关系图(引用自MySQL官方手册):

MySQL执行计划详解(EXPLAIN)

MySQL官方文档中关于此数据库的页面为https://dev.mysql.com/doc/employee/en。里面详细介绍了此数据库,并提供了下载地址和导入方法,如果有兴趣导入此数据库到自己的MySQL可以参考文中内容。

二、EXPLAIN OUTPUT FORMAT

EXPLAIN(执行计划)在MySQL中作用非常大,在EXPLAIN的帮助下,你就知道什么时候该给表添加索引,以及使用什么样的索引来查找记录从而让查询运行更快。如果由于不恰当使用索引而引起一些问题的话,可以运行analyze table来更新该表的统计信息(会锁表),如索引键的基数(Cardinality),它能帮您在优化方面做出更好的选择。

EXPLAIN返回了一行记录,它包括了查询语句中用到的各个表的信息。这些表在结果中按照MySQL即将执行的查询中读取的顺序列出来。MySQL用一次扫描多次联接(single- sweep,multi-join)的方法来解决联接。这意味着MySQL从第一个表中读取一条记录,然后在第二个表中查找到对应的记录,然后在第三个表中查找,依次类推。当所有的表都扫描完了,它输出选择的字段并且回溯所有的表,直到找不到为止,因为有的表中可能有多条匹配的记录下一条记录将从该表读取,再从下一个表开始继续处理。

EXPLAIN结果的每行记录显示了每个表的相关信息,每行记录都包含以下几个字段:

mysql> explain select * from salaries limit 1;
+----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------+
|  1 | SIMPLE      | salaries | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2838426 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------+
1 row in set, 1 warning (0.01 sec)

1. id

SELECT 标识符,SQL执行的顺序的标识,SQL从大到小的执行。

* id相同时,执行顺序由上至下。

* 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。

* 如果id相同,则认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。

2. select_type

select_type列提供了各种表示table列引用的使用方式的类型。可能会有以下几种:

select_type Value JSON Name Meaning
SIMPLE None 对于不包含子查询或复杂查询的简单查询,这是一个常见的类型。
PRIMARY None 这是为更复杂的查询而创建的首要表(也就是最外层的表),这个类型通常可以在DERIVED和UNION类型混合使用时见到。
UNION None 表示内层表,在select之后使用了union。
DEPENDENT UNION dependent (true) 表示union语句中的第二个select,依赖于外部子查询。
UNION RESULT union_result 从一个union返回结果。
SUBQUERY None 表示子查询为独立子查询,不依赖外部查询。
DEPENDENT SUBQUERY dependent (true) 表示子查询中的第一个子查询依赖于外部的子查询,也称之为相关子查询,速度是子查询中最慢的。
DERIVED None 当一个表不是物理表时,那么就被叫做derived,称为派生表(from子句中的子查询,是从子查询中派生出来的虚拟表中产生的)。形式:FROM (subquery expression) AS derived_table_alias。
MATERIALIZED materialized_from_subquery 表示物化子查询,MySQL引入了Materialization(物化)这一关键特性用于子查询(比如在IN/NOT IN子查询以及 FROM 子查询)优化。具体实现方式是:在SQL执行过程中,第一次需要子查询结果时执行子查询并将子查询的结果保存为临时表 ,后续对子查询结果集的访问将直接通过临时表获得。 与此同时,优化器还具有延迟物化子查询的能力,先通过其它条件判断子查询是否真的需要执行。物化子查询优化SQL执行的关键点在于对子查询只需要执行一次。 与之相对的执行方式是对外表的每一行都对子查询进行调用,其执行计划中的查询类型为“DEPENDENT SUBQUERY”。
UNCACHEABLE SUBQUERY cacheable (false) 子查询的结果不能被缓存,必须重新评估每行的外部查询。
UNCACHEABLE UNION cacheable (false) union中的第二个或后面的select属于UNCACHEABLE SUBQUERY。

3. table

这个值是记录查询引用的表名,表的别名或者一个为查询产生临时表的标识符,如派生表、子查询和集合。输出引用行的表的名字,有以下几种情况:

<unionM,N>:这行指的是联合查询中id值在M和N之间的行。

<derivedN>:这行指出派生表结果集在这行中的id值为N。

<subqueryN>:该行指的是物化查询在这行的ID值为N。

partitions:查询匹配的记录分区,只有在分区关键词被使用时,这列才会显示。未分区的表值为空。

4. type

type列表示执行计划中指定表使用的访问路径方式,以下列出了各种不同类型的MySQL访问路径方式,依次是从最好的到最差的:

system(系统表)

这个值表示,表只有一行记录(等于系统表),这是const表联接类型的一个特例。

const(单表单条记录查询)

这个值表示,表中最多只有一行匹配的记录,它在查询一开始的时候就会被读取出来。由于只有一行记录,在余下的优化程序里该行记录的字段值可以被当作是一个恒定值。const表查询起来非常快,因为只要读取一次!

const用于用常数值比较PRIMARY KEY或UNIQUE索引的所有部分时。在下面的查询中,tbl_name可以用于const表:

这个值表示出现在要联接过个表的查询计划中,驱动表只返回一行数据,且这行数据是第二个表的主键或者唯一索引,且必须为not null,唯一索引和主键是多列时,只有所有的列都用作比较时才会出现eq_ref。除了system和const类型之外,这是最好的联接类型。

eq_ref可以用于使用“=”操作符比较的带索引的列。比较的值可以是固定值或者是表达式,表达式中可以使用表里的字段。

以下的几个例子中,MySQL使用了eq_ref联接来处理ref_table:

ref(单表Index范围扫描)

该表中所有符合检索值的记录都会被取出来和从上一个表中取出来的记录做联接。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY(换句话说,如果联接不能基于关键字选择单个行的话),则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。

ref还可以用于检索字段使用”=”操作符来比较的时候。以下的几个例子中,MySQL将使用ref联接来处理ref_table:

使用FULLTEXT索引执行联接。

ref_or_null

该联接类型类似ref,不同的是MySQL会在检索的时候额外的搜索包含null值的记录。这种联接类型的优化是从MySQL 4.1.1开始的,它经常用于子查询优化。在以下的例子中,MySQL使用ref_or_null联接来处理ref_table:

表示查询使用了两个以上的索引,最后取交集或者并集,常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取多个索引,性能可能大部分时间都不如range。

unique_subquery

该类型替换了下面形式的IN子查询的ref:value in (select primary_key from single_table where some_expr)。

unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。

index_subquery

该联接类型类似于unique_subquery,可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:value in (select key_column from single_table where some_expr)。

range(Index range scan)

这个类型表示只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。

当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range。

例如:

index(Index Tree Scan)

此类型表示扫描整个索引树,其联接类型跟all(全表扫描)差不都,但分两种情况:

  • 如果索引是覆盖索引,并且可用于满足查询中所需的所有数据,则仅扫描索引树。在这种情况下,Extra列显示Using index。仅扫描索引通常比ALL快,因为索引的大小通常小于表数据。
  • 执行全表扫描,以按索引顺序查找数据行。Using index不会出现在Extra列中。

当查询只使用作为单索引一部分的列时,MySQL可以使用该联接类型。

all(全表扫描)

此类型表示将对该表做全表扫描。这时候如果第一个表没有被标识为const的话就不大好了,在其他情况下通常是非常糟糕的。正常地,可以通过增加索引使得能从表中更快的取得记录以避免all。一般就算有利用到索引时,也可能会出现全表扫描的情况,当需要范围的数据占总表的百分之25左右时,优化器会选择全表扫描(主键除外),这是因为,全表扫描是顺序读数据,是顺序IO。

Type类型出现的值依次排序:system->const->eq_ref->ref->ref_or_null->unique_subquery->index_subquery->range->index_merge->index->all,越往后可以理解为开销越大。

Type类型出现的值依次排序:system->const->eq_ref->ref->ref_or_null->unique_subquery->index_subquery->range->index_merge->index->all,越往后可以理解为开销越大。

5. possible_keys

表示MySQL查找表中的行时可选择的索引。请注意,此列完全独立于EXPLAIN输出中显示的顺序。 这意味着在possible_keys中的某些键实际上不能按生成的表顺序使用。

如果该列是NULL,则代表没有相关的索引。在这种情况下,可以通过检查WHERE子句看它是否引用了某些列或适合索引的列来提高查询性能。如果是这样,那么就需要创造一个适当的索引,并再次用EXPLAIN检查。

6. key

显示MySQL实际决定使用的键(索引),如果MySQL决定使用其中一个possible_keys 索引来查找行,则该索引被列为关键值。

如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

对于InnoDB而言,即便是查询也选择主键索引,辅助索引(secondary index)可能会覆盖所选列,因为InnoDB将主键值存储在每个辅助索引中。如果key为NULL,则代表MySQL未发现可用于提高效率的索引。

对于MyISAM的表,运行 ANALYZE TABLE 有助于优化器选择更好的索引。myisamchk –analyze 也是如此。

7. key_len

显示MySQL使用索引键的长度,就是此次查询所选择的索引长度有多少字节。通常我们可借此判断联合索引有多少列被选择了,对于确认索引的有效性很重要。索引的最大长度为128位。

在这里 key_len 大小的计算规则是:

  • 一般地,key_len等于索引列类型字节长度,例如int类型为4 bytes,bigint为8 bytes;
  • 如果是字符串类型,还需要同时考虑字符集因素,例如:CHAR(30) UTF8则key_len至少是90 bytes;
  • 若该列类型定义时允许NULL,其key_len还需要再加1 bytes;
  • 若该列类型为变长类型,例如VARCHAR(TEXT\BLOB不允许整列创建索引,如果创建部分索引,也被视为动态列类型),其key_len还需要再加2 bytes;

综上,看下面几个例子:

列类型 KEY_LEN 备注
id int key_len = 4+1 = 5 允许NULL,加1 byte
id int not null key_len = 4 不允许NULL
user char(30) utf8 key_len = 30*3+1 允许NULL
user varchar(30) not null utf8 key_len = 30*3+2 动态列类型,加2 bytes
user varchar(30) utf8 key_len = 30*3+2+1 动态列类型,加2 bytes;允许NULL,再加1 byte
detail text(10) utf8 key_len = 30*3+2+1 TEXT列截取部分,被视为动态列类型,加2 bytes;且允许NULL

备注,key_len只指示了WHERE中用于条件过滤时被选中的索引列,是不包含ORDER BY/GROUP BY这部分被选中的索引列。

例如,有个联合索引idx1(c1, c2, c3),3个列均是INT NOT NULL,那么下面的这个SQL执行计划中,key_len的值是8而不是12:SELECT…WHERE c1=? AND c2=? ORDER BY c1。

8. ref

ref字段标识哪些字段或者常量被用来和key配合从表中查询记录出来,如果为NULL表示没有。

9. rows

表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录InnoDB层所需要读取的行数。这个数字是个约数(根据MySQL内部统计信息来估算的),并不能总是很精确。最好的估值是1,一般来说这种情况发生在当寻找的行在表中可以通过主键或者唯一键找到的时候。

10. filtered

它指返回结果的行数(MySQL层where过滤生效的数据量)占需要扫描到的行数(rows列的值)的百分比,一般来说越高越好,越低证明查询代价越高。按说filtered是个非常有用的值,因为对于join操作,前一个表的结果集大小直接影响了循环的次数(rows * filtered / 100可以作为前一个表的返回行的参考值)。 filtered值只对index和all的扫描有效;这可以理解,其它场合,通常rows值就等于估算的结果集大小。

比如下面这个执行计划:

利用公式8064 * 68.28 / 100约等于5506,也就是说经过where条件过滤后返回的文章条目数大概就5506条。我们看一下实际条目相差多少:

在MySQL 5.7.3以前,如果你使用EXPLAIN EXTENDED,filtered这列会显示。到了MySQL 5.7.3,扩展输出默认开启,扩展关键词也不是必须的(废除了)。

11. extra

Extra列提供了有关不同种类的MySQL优化器路径的一系列额外信息。Extra列可以包含多个值,可以有很多不同的取值,并且这些值还在随着MySQL新版本的发布而进一步增加。下面给出常用值的列表:

distinct

这个值意味着MySQL在找到第一个匹配的行之后就不再搜索其他记录了。

not exists

mysql在查询时做一个 left join优化时,当它在当前表中找到了和前一条记录符合 left join条件后,就不再搜索更多的记录了,下面是一个这种类型的查询例子:

假使 t2.id 定义为 not null。这种情况下,mysql将会扫描表 t1并且用 t1.id 的值在 t2 中查找记录。当在 t2中找到一条匹配的记录时,这就意味着 t2.id 肯定不会都是null,就不会再在 t2 中查找相同 id值的其他记录了。也可以这么说,对于 t1 中的每个记录,mysql只需要在t2 中做一次查找,而不管在 t2 中实际有多少匹配的记录。

mysql没找到合适的可用的索引。取代的办法是,对于前一个表的每一个行联接,它会做一个检验以决定该使用哪个索引(如果有的话),并且使用这个索引来从表里取得记录。这个过程不会很快,但总比没有任何索引时做表联接来得快。

using filesort

这是ORDER BY语句的结果,这可能是一个CPU密集型的查询过程。其排序程序根据联接的类型遍历所有的记录,并且将所有符合where条件的记录的要排序的键和指向记录的指针存储起来。这些键已经排完序了,对应的记录也会按照排好的顺序取出来。可以通过选择合适的索引来改进性能,用索引来为查询结果排序,索引本身就是排序的,索引速度很快。MySQL可以直接使用索引来满足一个ORDER BY或GROUP BY子句而无需做额外的排序。

using where

这个值表示查询使用了where(严格来说是做了index filter)语句来处理结果,例如执行全表扫描。如果用到了索引,那么行的限制条件是通过获取必要的数据之后处理缓冲区来实现的。我个人理解using where表示优化器需要通过索引回表查询数据。如下例子:

搞明白Using where之前,必须先搞明白根据何登成大神总结出一套放置于所有SQL语句而皆准的where查询条件的提取规则:所有SQL的where条件,均可归纳为3大类:Index Key (First Key & Last Key),Index Filter,Table Filter。接下来,简单说一下这3大类分别是如何定义,以及如何提取的,详情请看:SQL语句中,where条件在数据库中提取与应用浅析

using index

这个值重点强调了只需要使用索引就可以满足查询表的要求,不需要直接访问表数据了,一般表示使用了覆盖索引。

using where; using index

表示使用到了覆盖索引,同时又使用到了index filter进行数据过滤。

using index condition

此值表示优化器使用了ICP对数据访问进行优化。在MySQL 5.6版本后加入的新特性(Index Condition Pushdown,ICP),是一种在存储引擎层使用索引过滤数据的一种优化方式。我对Using index condition 的理解是,首先 mysql server 和 storage engine 是两个组件,server 负责 sql的parse,执行; storage engine 去真正的做数据/index的读取/写入。以前是这样: server 命令 storage engine 按 index 把相应的数据从数据表读出,传给server,server来按where条件做选择;现在 ICP则是在可能的情况下让storage engine根据index做判断,如果不符合条件则无须读数据表,这样节省了disk IO。具体看:MySQL ICP(Index Condition Pushdown)特性

using join buffer

这个值强调了在获取联接条件时没有使用索引,并且需要联接缓冲区来存储中间的结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进性能。

using temporary

这个值表示使用了内部临时表(基于内存的表),一个查询可能用到多个临时表,有很多原因都会导致mysql在执行查询期间需要创建临时表存储结果以完成查询。这种情况通常发生在查询时包含了group by和order by子句,或者来自不同表的列使用了distinct。

select tables optimized away

这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回这一行。如下示例:select count(*) from (select …) c。

Using MRR

使用多范围读取的优化策略来读取表中的数据。

三、EXPLAIN OUTPUT JSON FORMAT

在MySQL 5.7中EXPLAIN支持了JSON格式输出,输出内容跟普通内容有些不同,输出如下:

可以跟上面输出的普通格式对比一下,如下表:

Column JSON Name Meaning
id select_id 选择标识符
select_type None 选择类型
table table_name 输出行的表
partitions partitions 匹配的分区
type access_type 联接类型
possible_keys possible_keys 可能用到的索引
key key 实际用到的索引
key_len key_length 用到索引的长度
ref ref 列对应的索引
rows rows 预测要检查的行数
filtered filtered 表条件筛选行的百分比
Extra None 额外的信息

JSON格式的,明显可以看出多了query_cost、used_key_parts。其中query_cost表示查询代价,代价越小效率越高,代价如何评估是MySQL优化器做的事。具体还没有深入,后面好好看看。

<扩展>

https://segmentfault.com/a/1190000012629884

猜你喜欢

转载自www.cnblogs.com/llill/p/9318490.html