在MySQL数据库中,是使用EXPLAIN命令来查看执行计划的。下面具体说明一下它的用法。
一、使用方法
1.语法格式(5.6版本)
EXPLAIN [explain_type] explainable_stmt
可选项包括:
EXTENDED|PARTITIONS|FORMAT=format_name
format_name: TRADITIONAL|JSON
2.说明
EXPLAIN QUERY
当在一个Select语句前使用关键字EXPLAIN时,MYSQL会解释了即将如何运行该Select语句,它显示了表如何连接、连接的顺序等信息。
EXPLAIN EXTENDED QUERY
当使用EXTENDED关键字时,EXPLAIN产生附加信息,可以用SHOW WARNINGS浏览。该信息显示优化器限定SELECT语句中的表和列名,重写并且执行优化规则后SELECT语句是什么样子,并且还可能包括优化过程的其它注解。在MySQL5.0及更新的版本里都可以使用,在MySQL5.1里它有额外增加了一个过滤列(filtered)。
EXPLAIN PARTITIONS QUERY
显示的是查询要访问的数据分片——如果有分片的话。它只能在MySQL5.1及更新的版本里使用。
EXPLAIN FORMAT=JSON (5.6新特性)
另一个格式显示执行计划。可以看到诸如表间关联方式等信息。
3.示例
explain select * from tmp_users where uid = 9527 and l_date >='2012-12-10'\G
************** 1. row *******************
id: 1
select_type: SIMPLE
table: tmp_users
type: ref
possible_keys: ind_uidldate
key: ind_uidldate
key_len: 4
ref: const
rows: 418
Extra: Using where
二、输出字段
1.id
MySQL Query Optimizer选定的执行计划中查询的序列号。
说明
(1) ID列总是包含一组数字,根据它可识别出SELECT是属于哪一行。
(2) 如果语句里没有子查询或者联接,那整个输出里就只有一个SELECT,这样一来,每一行在这个列上都会显示一个1。
(3) 内层的SELECT语句通常按它们在原始语句的位置顺序来编号。
(4) 在FROM子句和UNION里的子查询会给ID列带来很大的复杂性。
(5) 如果查询计划中使用到了临时表,MySQL内部会通过外层查询里的一个别名来引用这个临时表,这种现象可以在复杂查询的relfof列里看到。
2.select_type
所使用的查询类型。是简单SELECT还是复杂SELECT(如果是后者,显示它属于哪一种复杂类型)。值SAMPLE意味着这个查询里没有子查询或者UNION。如果查询里有了任何复杂的子部分,最外层部分就被标记为PRIMARY,其余部分会用以下几种标记类型:
DEPENDENT SUBQUERY
子查询内层的第一个SELECT,依赖于外部查询的结果集。
DEPENDENT UNION
子查询中的UNION,且为UNION中从第二个SELECT开始的后面所有SELECT,同样依赖于外部查询的结果集。
PRIMARY
子查询中的最外层查询,注意并不是主键查询。
SIMPLE
除子查询或UNION之外的其他查询。
SUBQUERY
子查询内层查询的第一个SELECT,结果不依赖于外部查询结果集。
UNCACHEABLE SUBQUERY
结果集无法缓存的子查询。
UNION
UNION语句中的第二个SELECT开始后面的所有SELECT,第一个SELECT为PRIMARY。
UNION RESULT
UNION中的合并结果。从UNION临时表获取结果的SELECT。
DERIVED
衍生表查询(FROM子句中的子查询)。MySQL会递归执行这些子查询,把结果放在临时表里。在内部,服务器就把当做一个"衍生表"那样来引用,因为临时表就是源自子查询。
3.table
说明
(1) 这一步所访问的数据库中表的名称或者SQL语句指定的一个别名表。
(2) 这个值可能是表名、表的别名或者一个为查询产生的临时表的标识符,如派生表、子查询或集合。
表间关联顺序
可以从上到下读这一列,查看优化器为这个查询生成的连接次序。
衍生表和联合
(1) 当FROM子句里有子查询或者有UNION时,表的列表会变得更加复杂。在这种情况下,它引用的不是真正的"表",因为MySQL创建的临时表只在查询执行的时候存在。
(2) 当有一个子查询在FROM子句的时候,table列就变成<derivedN>的形式,这里的N就是子查询的id。有一个"前向引用"总是存在——换句话说,N引用的是EXPLAIN输出的下一行。
(3) 当有UNION时,UNION RESULT的列里面就会包含一个id列表,这些id都出现在UNION里。这里总会有一个"后向引用",因为UNION RESULT是紧跟着UNION里的那些行之后出现的。如果列表里有超过20个id,那table列就会被删减,以免太长,而你也就看到所有的值了。幸运的是,你仍然可以推算出那些行被包括进去了,因为你能看到第一行的id。所有来自该行和UNION RESULT之间的任何东西都会通过某种方式被包含进来。
4.type
表的访问方式。以下列出了各种不同类型的表连接,依次是从最好的到最差的。
system
系统表,表只有一行记录。这是const表连接类型的一个特例。
const
(1) 读常量,最多只有一行匹配的记录。const表查询起来非常快,因为只要读取一次。它在查询一开始的时候就会被读取出来。由于只有一行记录,优化程序里该行记录的字段值可以被当作是一个恒定值。const用于在和PRIMARY KEY或UNIQUE索引中有固定值比较的情形。
(2) 当MySQL能对查询的某部分进行优化,并转换成一个常量时,它就会使用这些访问类型。举例来说,如果你选择了某一行的主键放入WHERE子句里,MySQL就能把这个查询转换为一个常量。然后就可以移除表的联接,更加有效地执行。
eq_ref
(1) 最多只会有一条匹配结果,一般是通过主键或唯一键索引来访问。从该表中会有一行记录被读取出来以和从前一个表中读取出来的记录做联合。与const类型不同的是,这是最好的连接类型。它用在索引所有部分都用于做连接并且这个索引是一个PRIMARY KEY或UNIQUE类型。eq_ref可以用于在进行"="做比较时检索字段。比较的值可以是固定值或者是表达式,表达示中可以使用表里的字段,它们在读表之前已经准备好了。
(2) 使用这种索引查找,MySQL最多只返回一条符合条件的记录。这种访问方法你会在MySQL使用主键或者唯一性索引查找时看到,它将会它们与某个参考值作比较。MySQL对这类访问类型的优化做的非常好,因为它知道它无须估计匹配行的范围,也不用在找到匹配行后再继续查找。
ref
(1) JOIN语句中驱动表索引引用的查询。该表中所有符合检索值的记录都会被取出来和从上一个表中取出来的记录作联合。ref用于连接程序使用键的最左前缀或者是该键不是PRIMARY KEY或UNIQUE索引(换句话说,就是连接程序无法根据键值只取得一条记录)的情况。当根据键值只查询到少数几条匹配的记录时,这就是一个不错的连接类型。ref还可以用于检索字段使用"="操作符来比较的时候。
(2) 这是一种索引访问(有时也叫做索引查找),它返回所有匹配某个单独值的行。然而,它可能会找到多个符合条件的行,因此它是查找和扫描的混合体。此类索引访问只有当使用一个非唯一性索引或唯一性索引的非唯一性前缀时才会发生。把它叫做ref是因为索引要跟某个参考值相比较。这个参考值是一个常数或者是来自一个表里的多表查询的结果值。
ref_or_null
(1) 与ref的唯一区别就是在使用索引引用的查询之外再增加一个空值的查询。这种连接类型类似ref,不同的是MySQL会在检索的时候额外的搜索包含NULL值的记录。这种连接类型的优化是从MySQL 4.1.1开始的,它经常用于子查询。
(2) 是ref之上的一个变体,它意味着MySQL必须进行二次查找,在初次查找的结果里找出NULL条目。
index_merge
查询中同时使用两个(或更多)索引,然后对索引结果进行合并(merge),再读取表数据。这种连接类型意味着使用了Index Merge优化方法。这种情况下,key字段包括了所有使用的索引,key_len包括了使用的键的最长部分。
unique_subquery
子查询中的返回结果字段组合是主键或唯一约束。这种类型用例如以下形式的IN子查询来替换ref。
index_subquery
子查询中的返回结果字段组合是一个索引(或索引组合),但不是一个主键或唯一索引。这种连接类型类似unique_subquery。它用子查询来代替IN,不过它用于在子查询中没有唯一索引的情况下,例如以下形式。
range
(1) 索引范围扫描。只有在给定范围的记录才会被取出来,利用索引来取得一条记录。
(2) 范围扫描就是一个有限制的索引扫描,它开始于索引里的某一点,返回匹配那个值域的行。这比全索引扫描好一些,因为它用不着遍历全部索引。常见的范围扫描就是WHERE子句里带有BETWEEN或>的查询。当MySQL使用一个索引来查找一系列值时,例如IN()和OR列表,它也会显示为范围扫描。然而,这两者其实是相当不同的访问类型,在性能上有重要的差异。
index
(1) 全索引扫描。连接类型跟ALL一样,不同的是它只扫描索引树。它通常会比ALL快点,因为索引文件通常比数据文件小。MySQL在查询的字段知识单独的索引的一部分的情况下使用这种连接类型。
(2) 这个跟全表扫描一样,只是MySQL扫描表时按索引次序进行而不是行。它的主要优点是避免了排序,最大的缺点是要承担按索引次序读取整张表的开销。这通常意味着若是按随机次序访问呢行,开销将会非常大。
(3) 如果在Extra列里"使用索引",这说明MySQL正在使用一个覆盖索引,它只扫描索引的数据,而不是按索引次序的每一行。在开销方面比按索引次序的全表扫描少很多。
fulltext
全文索引扫描。
all
全表扫描。将对该表做全部扫描以和从前一个表中取得的记录作联合。这时候如果第一个表没有被标识为const的话就不大好了,在其他情况下通常是非常糟糕的。正常地,可以通过增加索引使得能从表中更快的取得记录以避免ALL。
NULL
这种访问方式意味着MySQL能在优化过程中分解查询语句,在执行环节里,甚至用不着再访问表或者索引。举例来说,从一个索引列里选取最小值可以通过单独查找索引来完成,不需要在执行时访问表。
5.possible_keys
(1)该查询可以利用的索引。如果没有任何索引可以使用,就会显示为null。
(2)possible_keys字段是指MySQL在搜索表记录时可能使用哪个索引。注意,这个字段完全独立于EXPLAIN显示的表顺序。这就意味着possible_keys里面所包含的索引可能在实际的使用中没用到。如果这个字段的值是NULL,就表示没有索引被用到。这种情况下,就可以检查where子句中哪些字段那些字段适合增加索引以提高查询的性能。就这样,创建一下索引,然后再用EXPLAIN检查一下。
(3)这一列显示了基于查询能够访问的列和选用的比较操作类型来决定哪个索引可以被用作查询。这个列表是在优化过程的早期被创建的,因此有些罗列出来的索引可能对后续优化过程是没用的。
6.key
(1)查询优化器从possible_keys中所选择使用的索引。key字段显示了MySQL实际上要用的索引。当没有任何索引被用到的时候,这个字段的值就是NULL。想要让MySQL强行使用或者忽略在possible_keys字段中的索引列表,可以在查询语句中使用关键字FORCE INDEX、USE INDEX或IGNORE INDEX。如果是MyISAM和BDB类型表,可以使用ANALYZE TABLE来帮助分析使用使用哪个索引更好。如果是MyISAM类型表,运行命令myisamchk --analyze也是一样的效果。
(2)这一列显示了MySQL采用了哪一个索引来优化对该表的访问。如果该索引没有出现在possible_keys列,MySQL选用它是处于另外一个原因。例如,它可能选择了一个覆盖索引,哪怕没有WHERE子句。换句话说,possible_keys揭示了哪一个索引有助于行查找更加有效,而key显示的是优化器采用哪一个索引可以最小化查询成本。
(3)一般来说SQL查询中的每个表仅使用一个索引,但也存在索引合并的例外情况。
7.key_len
(1)被选中使用索引的索引键长度。key_len字段显示了MySQL使用索引的长度。当key字段的值为NULL时,索引的长度就是NULL。注意,key_len的值可以告诉在联合索引中MySQL会真正使用了哪些索引。
(2)该列显示MySQL在索引里使用的字节数。如果MySQL正在使用的索引列里的某一个,则可以从这个值来计算出来。要记住MySQL只能只用索引的最左边前缀。
(3)MySQL并不总是能显示出一个索引的真正被使用的次数。举例而言,如果你使用前缀模式匹配的like查找,它就会显示出所使用列的全宽度。
(4)key_len列显示的是索引字段的最大可能长度,不是表内数据使用的真正字节数。在下例(CHAR)中,MySQL总是显示13个字节,哪怕是a列没有数据,连一个字符都没有。换句话说,key_len是根据表的定义计算出来的,而不是通过表内数据检索出的。
(5)key_len列的值只和用在连接和WHERE条件中的索引的列有关。索引中的其他列会在ORDER BY或者GROUP BY语句中被用到。
数据类型说明
(1) 如果字段是INT类型,则占用了4 Bytes;如果可为NULL,则还需要用一个字节标识是否为空。也就是说,如果这个字段有索引且被是使用,则key_len=4+1。
(2) 如果字段是VARCHAR类型,会额外多用两个字段标识字段长度。
(3) 如果字段是多字节字符集的话,gbk*2 utf8*3
(4) 其他字段如datetime占用8个字节,bigint占用8个字节,CHAR(M)占用M*w个字节
8.ref
(1)列出是通过常量,还是某个表的某个字段(如果是join)来过滤(通过key)的。ref字段显示了哪些字段或者常量被用来和key配合从表中查询记录出来。
(2)这一列显示了哪些来自上述表里的列或者常量正在被用于查找key列上的值。
9.rows
(1) 查询优化器通过系统收集的统计信息估算出来的结果集记录条数。
(2) 显示的是MySQL估计的为了找到所需的行而要读取的行数。这个数字是内嵌循环联接计划里的循环数目。也就说说它不是MySQL认为它最终要从表里读取的行数,而是MySQL为了找到符合查询的每一点上标准的那些行而必须继续读取的行的平均数。(这个标准包含SQL里给定的条件,以及来自联接次序上前一个表的当前列)
(3) 根据表的统计信息和索引的选用情况,这个估算可以很精确。在MySQL5.0及更早版本,它不能反映出LIMIT子句。
(4) 把所有rows里的值都乘起来,可以粗略地估算出整个查询会检查的行数。要记住这个数字是MySQL认为它要检查的行数,不是结果集里的行数。同时也要认识到有很多优化手段,例如联接缓冲区和缓存,都无法影响到行数的显示。MySQL可能不必真的读入所有它估计到的行,它也不知道任何关于操作系统或硬件缓存的信息。
(5) rows列提供了试图分析所有存在于累计结果集中的行数目的MySQL优化器估算值。QEP很容易描述这个很困难的统计量。查询中总的读操作数量时基于合并之前行的每一行的rows值的连续累计而得出的。这是一种嵌套行算法。
10.Extra
本字段显示了查询中MySQL的附加信息。以下是这个字段的几个不同值的解释。
Distinct
查找distinct值,当mysql找到了第一条匹配的结果时,将停止该值的查询,转为后面其他值查询。
Not exists
MySQL在查询时做一个LEFT JOIN优化时,当它在当前表中找到了和前一条记录符合LEFT JOIN条件后,就不再搜索更多的记录了。
Range checked for each record (index map: #)
(1) 当MySQLQuery Optimizer没有发现好的可以使用的索引时,如果发现前面表的列值已知,部分索引可以使用。对前面表的每个行组合,MySQL检查是否可以使用range或index_merge访问方法来索取行。
(2) 这个值意味着没有好的索引可用,新的索引将在联接的每一行上被重新估算。N是显示在possible_keys列索引的位图,这是一个冗余。
Using filesort
(1) 当Query中包含ORDER BY操作,而且无法利用索引完成排序操作的时候,MySQL QueryOptimizer不得不选择相应的排序算法来实现。MySQL需要额外的做一遍从而以排好的顺序取得记录。排序程序根据连接的类型遍历所有的记录,并且将所有符合where条件的记录的要排序的键和指向记录的指针存储起来。这些键已经排完序了,对应的记录也会按照排好的顺序取出来。
(2) 这意味着MySQL会对结果使用一个外部索引排序,不是从表里按索引次序来读取行。MySQL两种文件排序算法,任何一种方式都可以在内存或磁盘上进行。EXPLAIN无法告诉你MySQL将使用的哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上进行。
Using index
(1) 所需数据只需在index即可全部获得,不需要再到表中取数据,即"索引覆盖扫描"。字段的信息直接从索引树中的信息取得,而不再去扫描实际的记录。这种策略用于查询时的字段是一个独立索引的一部分。
(2) 该值表示MySQL将使用覆盖索引,以避免访问表。不要把覆盖索引和索引访问类型弄混了。
Using index for group-by
数据访问和Using index一样,所需数据只须要读取索引,当Query中使用GROUP BY或DISTINCT子句时,如果分组字段也在索引中,Extra中的信息就会是Using index for group-by。
Using temporary
当MySQL在某些操作中必须使用临时表时,在Extra信息中就会出现Using temporary。主要常见于GROUP BY和ORDER BY等操作中。
Using where
(1) 如果不读取表的所有数据,或不是仅仅通过索引就可以获取所有需要的数据,则会出现Using where信息。Where子句将用来限制哪些记录匹配了下一个表或者发送给客户端。除非特别地想要取得或者检查表中的所有记录,否则的话当查询的Extra字段值不是Using where并且表连接类型是ALL或index时可能表示有问题。如果想要让查询尽可能的快,那么就应该注意Extra字段的值为Using filesort和Using temporary的情况。
(2) 这意味着MySQL服务器将在存储引擎收到行后进行后过滤。很多WHERE条件里包含的属于索引的列,当它(如果它)读取索引的时候,就能被存储引擎检验。因此,不是所有带WHERE子句的查询都会显示"Using where"。有时"Usingwhere"的出现就是一个提示:查询可受益于不同的索引。
Using where with pushed condition
这是一个仅仅在NDB Cluster存储引擎中才会出现的信息,而且还需要通过打开Condition Pushdown优化功能才可能被使用。控制参数为engine_condition_pushdown。
Full scan on NULL key
子查询中的一种优化方式,主要在遇到无法通过索引访问null值的使用。
Impossible WHERE noticed after reading const tables
查询优化器通过收集到的统计信息判断出不可能存在结果。
No tables
Query语句中使用FROM DUAL或不包括任何FROM子句。
SELECT tables optimized away
当使用某些聚合函数来访问存在索引的某个字段时,查询优化器会通过索引直接一次定位到所需的数据行完成整个查询。当然,前提是在Query中不能有GROUP BY操作。如使用MIN/MAX的时候。
11.filtered
这个列式在MySQL5.1里新加进去的,当你使用EXPLAIN EXTENDED时才会出现。它显示的是针对表里符合某个条件(WHERE子句或联接条件)的记录数的百分比所作的一个悲观估算。如果把rows列和这个百分比相乘,就能看到MySQL估算的它将和查询记录的前一个表联接的行数。目前,优化器只有在使用ALL、INDEX、INDEX_MERGE访问方法时才会用估算。
三、其它说明
1.估算连接表现
你可以通过EXPLAIN的结果中rows字段的值的乘积大概地知道本次连接表现如何。它可以粗略地告诉我们MySQL在查询过程中会查询多少条记录。如果是使用系统变量max_join_size来取得查询结果,这个乘积还可以用来确定会执行哪些多表Select语句。
2.EXPLAIN是否会执行SQL
一个常见的误解是在查询里添加了EXPLAIN后,MySQL不会真正地执行它。事实上,如果在FROM子句里包含了一个子查询的话,MySQL还是会执行这个子查询,并把查询结果放入一个临时表里,然后完成对外层查询的优化工作。在它完成外层查询的全部优化工作之前,它就是这样处理所有子查询,这是针对EXPLAIN必须要做的事情。这意味着如果查询语句里包含了使用TEMPTABLE算法的开销昂贵的子查询或视图时,EXPLAIN也能引发服务器的大量的处理任务。
3.局限性
(1) EXPLAIN不会告诉你任何关于触发器、存储函数或UDF对查询的影响情况。
(2) 对于存储过程,它是没有用的,但是可以手工分离出查询语句,然后用EXPLAIN一条条单独执行。
(3) 它不会告诉你MySQL在查询执行时所做的那些优化工作。
(4) 一些现实的饿统计信息是估算的,不是很精确。
(5) 它无法显示出一个查询计划里的所有信息。
(6) 它无法区分同名的一些东西。
(7) 它有时会误导你。