MySQL如何使用EXPLAIN优化SQL:输出内容参考手册
这篇文章差不多是MySQL官网的翻译版本,有任何不清楚的,或者翻译不到位的地方,都可以直接去官网查询原文档。
MySQL 8.0 Reference Manual - EXPLAIN Output Format
EXPLAIN语句提供了有关MySQL如何执行语句的信息。EXPLAIN适用于SELECT、DELETE、INSERT、REPLACE和UPDATE语句。
EXPLAIN为SELECT语句中使用的每个表返回一行信息。它按照MySQL处理语句时读取它们的顺序列出表。这意味着MySQL先从第一个表中读取一行,然后在第二个表中找到匹配的行,接着在第三个表中找到匹配的行,依此类推。当所有表都被处理完毕时,MySQL输出选定的列,并通过表列表回溯,直到找到一个表,该表有更多匹配的行。接下来将从该表中读取下一行,并继续处理下一个表。
EXPLAIN的每个输出行提供有关一个表的信息。每行包含在表10.1"EXPLAIN输出列"中总结的值,并在表后面的详细描述中进行描述。列名显示在表的第一列中;当使用FORMAT=JSON时,第二列提供输出中显示的等效属性名称。
列名 | JSON 名称 | 含义 |
---|---|---|
id | select_id | SELECT标识符 |
select_type | None | SELECT类型 |
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属性为NULL,则不会在JSON格式的EXPLAIN输出中显示。
字段值解释
id (JSON名称: select_id)
SELECT标识符。这是查询中SELECT语句的顺序号。如果行引用其他行的联合结果,则该值可以为NULL。在这种情况下,table列显示类似于<unionM,N>的值,表示该行引用具有id值为M和N的行的联合。
select_type (JSON名称: 无)
SELECT的类型,可以是下表中显示的任何类型。JSON格式的EXPLAIN将SELECT类型作为query_block的属性公开,除非它是SIMPLE或PRIMARY。表中还显示了JSON名称(如果适用)。
select_type值 | JSON名称 | 含义 |
---|---|---|
SIMPLE | None | 简单SELECT(不使用UNION或子查询) |
PRIMARY | None | 最外层SELECT |
UNION | None | UNION中的第二个或后续SELECT语句 |
DEPENDENT UNION | dependent (true) | UNION中的第二个或后续SELECT语句,依赖于外部查询 |
UNION RESULT | union_result | UNION的结果 |
SUBQUERY | None | 子查询中的第一个SELECT |
DEPENDENT SUBQUERY | dependent (true) | 子查询中的第一个SELECT,依赖于外部查询 |
DERIVED | None | 派生表 |
DEPENDENT DERIVED | dependent (true) | 依赖于另一个表的派生表 |
MATERIALIZED | materialized_from_subquery | 物化子查询 |
UNCACHEABLE SUBQUERY | cacheable (false) | 无法缓存的子查询,对于外部查询的每一行都必须重新评估 |
UNCACHEABLE UNION | cacheable (false) | 属于无法缓存的子查询的UNION中的第二个或后续SELECT语句(参见UNCACHEABLE SUBQUERY) |
DEPENDENT通常表示使用相关子查询。
DEPENDENT SUBQUERY评估与UNCACHEABLE SUBQUERY评估不同。对于DEPENDENT SUBQUERY,子查询仅针对其外部上下文的不同变量值集合重新评估一次。对于UNCACHEABLE SUBQUERY,子查询对外部上下文的每一行重新评估一次。
当您使用EXPLAIN指定FORMAT=JSON时,输出没有直接相当于select_type的单个属性;query_block属性对应于给定的SELECT。大多数SELECT子查询类型的等效属性在适当时可用(例如,MATERIALIZED的materialized_from_subquery),并在适当时显示。没有JSON等效项简单或PRIMARY。
非SELECT语句的select_type值显示受影响表的语句类型。例如,DELETE语句的select_type是DELETE。
table (JSON名称: table_name)
输出行引用的表的名称。这也可以是以下值之一:
<unionM,N>:该行引用具有id值为M和N的行的联合。
:该行引用id值为N的行的派生表结果。派生表可能是由FROM子句中的子查询产生的。
:该行引用id值为N的行的物化子查询结果。
partitions (JSON名称: partitions)
查询匹配的记录将来自哪些分区。对于非分区表,该值为NULL。
type (JSON名称: access_type)
连接类型。
possible_keys (JSON名称: possible_keys)
possible_keys列指示MySQL可以从中选择以查找此表中的行的索引。请注意,此列与EXPLAIN输出中显示的表的顺序完全无关。这意味着possible_keys中的一些键在实际中可能无法使用。
如果此列为NULL(或在JSON格式的输出中未定义),则表示没有相关索引。在这种情况下,您可以通过检查WHERE子句是否引用了某些适合索引的列或列来改善查询性能。如果是这样,请创建一个适当的索引并再次使用EXPLAIN检查查询。
要查看表有哪些索引,可以使用SHOW INDEX FROM tbl_name
。
key (JSON名称: key)
key列指示MySQL实际决定使用的键(索引)。如果MySQL决定使用possible_keys索引之一来查找行,则该索引将列为key值。
如果key命名了一个在possible_keys值中不存在的索引,这是可能的。这可能发生在possible_keys索引中没有一个适合查找行,但查询选择的所有列都是某个其他索引的列的情况下。也就是说,命名的索引覆盖了所选列,因此虽然它不用于确定要检索哪些行,但索引扫描比数据行扫描更有效。
对于InnoDB,即使查询还选择了主键,辅助索引也可能覆盖所选列,因为InnoDB将主键值存储在每个辅助索引中。如果key为NULL,MySQL没有找到用于更有效执行查询的索引。
要强制MySQL使用或忽略possible_keys列中列出的索引,请在查询中使用FORCE INDEX、USE INDEX或IGNORE INDEX。
对于MyISAM表,运行ANALYZE TABLE有助于优化器选择更好的索引。对于MyISAM表,myisamchk --analyze做同样的事情。
key_len (JSON名称: key_length)
key_len列指示MySQL决定使用的键的长度。key_len的值使您能够确定MySQL实际使用了多部分键的多少部分。如果key列显示NULL,则key_len列也显示NULL。
由于键存储格式,可为NULL的列的键长度比NOT NULL列的键长度大1。
ref (JSON名称: ref)
ref列显示与key列中命名的索引进行比较以选择表中的行的哪些列或常量。
如果值是func,则使用的值是某个函数的结果。要查看哪个函数,请在EXPLAIN之后使用SHOW WARNINGS查看扩展的EXPLAIN输出。该函数实际上可能是一个运算符,例如一个算术运算符。
rows (JSON名称: rows)
rows列指示MySQL认为必须检查以执行查询的行数。
对于InnoDB表,这个数字是一个估计值,可能不总是精确的。
filtered (JSON名称: filtered)
filtered列指示表条件过滤的行的估计百分比。最大值是100,这意味着没有进行行过滤。从100递减的值表明进行了越来越多的过滤。rows显示估计的检查行数,rows × filtered显示与下一个表连接的行数。例如,如果rows是1000,filtered是50.00(50%),则与下一个表连接的行数是1000 × 50% = 500。
Extra (JSON名称: 无)
此列包含有关MySQL如何解析查询的附加信息。
没有直接对应于Extra列的单个JSON属性;然而,此列中可能出现的值作为JSON属性公开,或作为message属性的文本。
EXPLAIN连接类型
EXPLAIN输出的type列描述了表是如何连接的。在JSON格式的输出中,这些值作为access_type属性的值出现。下面的列表按从最好到最差的顺序描述了连接类型:
system
表只有一行(=系统表)。这是const连接类型的特例。
const
表最多有一行匹配的行,在查询开始时读取。因为只有一行,所以此行中的列值可以被优化器的其余部分视为常量。const表非常快,因为它们只被读取一次。
当您将PRIMARY KEY或UNIQUE索引的所有部分与常量值进行比较时,会使用const。在以下查询中,tbl_name可以用作const表:
sql
SELECT * FROM tbl_name WHERE primary_key=1;
SELECT * FROM tbl_name
WHERE primary_key_part1=1 AND primary_key_part2=2;
eq_ref
对于前面表的每个行组合,从这个表中读取一行。除了system和const类型之外,这是最好的可能的连接类型。当索引的所有部分都被连接使用,并且索引是PRIMARY KEY或UNIQUE NOT NULL索引时,会使用eq_ref。
eq_ref可用于使用=操作符比较的索引列。比较值可以是常量或使用在此表之前读取的表的列的表达式。在以下示例中,MySQL可以使用eq_ref连接来处理ref_table:
sql
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
ref
对于前面表的每个行组合,从这个表中读取所有具有匹配索引值的行。如果连接只使用键的最左前缀或如果键不是PRIMARY KEY或UNIQUE索引(换句话说,如果连接不能基于键值选择单个行),则使用ref。如果使用的键只匹配少数几行,这是一个好的连接类型。
ref可用于使用=或<=>操作符比较的索引列。在以下示例中,MySQL可以使用ref连接来处理ref_table:
sql
SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
fulltext
连接使用FULLTEXT索引执行。
ref_or_null
这种连接类型类似于ref,但是MySQL会额外搜索包含NULL值的行。这种连接类型优化最常用于解决子查询。在以下示例中,MySQL可以使用ref_or_null连接来处理ref_table:
sql
SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
index_merge
这种连接类型表示使用了索引合并优化。在这种情况下,输出行中的key列包含了使用的索引列表,key_len包含了使用的索引的最长键部分列表。
unique_subquery
这种类型替换了某些形式的IN子查询的eq_ref:
sql
value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery只是一个索引查找函数,它完全替换了子查询以提高效率。
index_subquery
这种连接类型类似于unique_subquery。它替换了IN子查询,但它适用于子查询中的非唯一索引:
sql
value IN (SELECT key_column FROM single_table WHERE some_expr)
range
只检索给定范围内的行,使用索引选择行。输出行中的key列
指示使用哪个索引。key_len包含了使用的最长键部分。这种类型的ref列为NULL。
range可用于当键列与常量使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN、LIKE或IN()操作符进行比较时:
sql
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
index
index连接类型与ALL相同,只是扫描索引树。这有两种方式发生:
如果索引是查询的覆盖索引,并且可以用来满足表中所需的所有数据,则只扫描索引树。在这种情况下,Extra列显示Using index。索引只扫描通常比ALL更快,因为索引的大小通常比表数据小。
使用索引中的读取以索引顺序查找数据行执行全表扫描。Extra列中不显示Uses index。
当查询仅使用属于单个索引的列时,MySQL可以使用这种连接类型。
ALL
对于前面表的每个行组合,都进行全表扫描。如果表是第一个未标记为const的表,这通常不好,在所有其他情况下通常非常糟糕。通常,通过添加允许根据常量值或早期表的列值从表中检索行的索引,可以避免ALL。
以下是对您提供的MySQL EXPLAIN额外信息的中文翻译和解释:
EXPLAIN额外信息
EXPLAIN输出的Extra列包含有关MySQL如何解析查询的额外信息。以下列表解释了该列中可能出现的值。每个项目还指出了在JSON格式的输出中显示Extra值的属性。对于其中一些值,有一个特定的属性。其他值显示为message属性的文本。
如果您想让查询尽可能快,那么请注意Extra列的值为Using filesort和Using temporary,或者在JSON格式的EXPLAIN输出中,using_filesort和using_temporary_table属性等于true。
Backward index scan (JSON: backward_index_scan)
优化器能够使用InnoDB表上的降序索引。与Using index一起显示。
Child of 'table' pushed join@1 (JSON: message text)
此表被引用为可以推送到NDB内核的连接中的表的子表。仅适用于NDB Cluster,当启用推送下连接时。
const row not found (JSON属性: const_row_not_found)
对于像SELECT ... FROM tbl_name这样的查询,表是空的。
Deleting all rows (JSON属性: message)
对于DELETE,一些存储引擎(如MyISAM)支持一种处理器方法,可以以简单快速的方式删除表中的所有行。如果引擎使用了这种优化,则显示此Extra值。
Distinct (JSON属性: distinct)
MySQL正在寻找不同的值,因此在找到第一个匹配行后,它会停止为当前行组合搜索更多行。
FirstMatch(tbl_name) (JSON属性: first_match)
半连接的FirstMatch连接快捷策略用于tbl_name。
Full scan on NULL key (JSON属性: message)
这发生在子查询优化中,当优化器无法使用索引查找访问方法时的后备策略。
Impossible HAVING (JSON属性: message)
HAVING子句始终为假,不能选择任何行。
Impossible WHERE (JSON属性: message)
WHERE子句始终为假,不能选择任何行。
Impossible WHERE noticed after reading const tables (JSON属性: message)
MySQL已经读取了所有const(和system)表,并注意到WHERE子句始终为假。
LooseScan(m...n) (JSON属性: message)
使用了半连接的LooseScan策略。m和n是键部分编号。
No matching min/max row (JSON属性: message)
没有行满足像SELECT MIN(...) FROM ... WHERE condition这样的查询的条件。
no matching row in const table (JSON属性: message)
对于具有连接的查询,有一个空表或没有行满足唯一索引条件的表。
No matching rows after partition pruning (JSON属性: message)
对于DELETE或UPDATE,在分区修剪后,优化器没有找到要删除或更新的内容。它在含义上类似于SELECT语句的Impossible WHERE。
No tables used (JSON属性: message)
查询没有FROM子句,或者有FROM DUAL子句。
对于INSERT或REPLACE语句,当没有SELECT部分时,EXPLAIN显示此值。例如,对于EXPLAIN INSERT INTO t VALUES(10),因为它等同于EXPLAIN INSERT INTO t SELECT 10 FROM DUAL,所以会出现这个值。
Not exists (JSON属性: message)
MySQL能够对查询进行LEFT JOIN优化,一旦它找到一个匹配LEFT JOIN条件的行,它就不会在这个表中检查更多行以获取前一个行组合。下面是可以以这种方式优化的查询类型的示例:
sql
SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id
WHERE t2.id IS NULL;
假设t2.id定义为NOT NULL。在这种情况下,MySQL扫描t1并使用t1
.id的值在t2中查找行。如果MySQL在t2中找到匹配的行,它就知道t2.id永远不可能为NULL,并且不会扫描t2中具有相同id值的其余行。换句话说,对于t1中的每一行,MySQL只需要在t2中进行一次查找,无论t2中实际匹配多少行。
在MySQL 8.0.17及更高版本中,这也可以表示WHERE条件的形式为NOT IN (子查询)或NOT EXISTS (子查询)已经内部转换为反连接。这会删除子查询并将其表带入顶层查询的计划中,提供改进的成本规划。通过合并半连接和反连接,优化器可以更自由地重新排序执行计划中的表,在某些情况下导致更快的计划。
通过在执行EXPLAIN后检查SHOW WARNINGS的Message列,或在EXPLAIN FORMAT=TREE的输出中,可以看到给定查询的反连接转换是否执行。
注意
反连接是半连接的补充table_a JOIN table_b ON condition。反连接返回table_a中的所有行,这些行在table_b中没有与condition匹配的行。
Plan isn't ready yet (JSON属性: none)
当优化器尚未完成为正在命名连接中执行的语句创建执行计划时,会出现此值与EXPLAIN FOR CONNECTION一起出现。如果执行计划输出包含多行,则任何行或所有行都可能具有此Extra值,具体取决于优化器确定完整执行计划的进度。
Range checked for each record (index map: N) (JSON属性: message)
MySQL没有找到好的索引来使用,但发现在前面的表的列值已知后,一些索引可能会被使用。对于前面表的每个行组合,MySQL检查是否可以使用范围或index_merge访问方法来检索行。这不是非常快,但比不使用索引进行连接要快。
索引从1开始编号,按照SHOW INDEX为表显示的顺序排列。索引映射值N是一个位掩码值,表示哪些索引是候选项。例如,值0x19(二进制11001)表示考虑索引1、4和5。
Recursive (JSON属性: recursive)
这表明该行适用于递归公共表达式的递归SELECT部分。
Rematerialize (JSON属性: rematerialize)
Rematerialize (X,...)显示在表T的EXPLAIN行中,其中X是任何横向派生表,当读取T的新行时,其重新物化被触发。例如:
sql
SELECT
...
FROM
t,
LATERAL (引用t的派生表) AS dt
...
每次处理t的新行时,派生表的内容都会重新物化以使其保持最新。
Scanned N databases (JSON属性: message)
这表明服务器在处理INFORMATION_SCHEMA表的查询时执行了多少次目录扫描,N的值可以是0、1或全部。
Select tables optimized away (JSON属性: message)
优化器确定1)最多应返回一行,以及2)要产生这一行,必须读取确定的一组行。当要读取的行可以在优化阶段读取时(例如,通过读取索引行),在查询执行期间就不需要读取任何表。
第一个条件在查询被隐式分组时得到满足(包含聚合函数但没有GROUP BY子句)。第二个条件在每个使用的索引上执行一次行查找时得到满足。读取的索引数决定了要读取的行数。
考虑以下隐式分组的查询:
sql
SELECT MIN(c1), MIN(c2) FROM t1;
假设MIN(c1)可以通过读取一个索引行来检索,MIN(c2)可以通过从不同索引读取一行来检索。也就是说,对于每个列c1和c2,存在一个索引,其中该列是索引的第一列。在这种情况下,返回一行,通过读取两个确定的行产生。
如果要读取的行不是确定的,则不会出现此Extra值。考虑这个查询:
sql
SELECT MIN(c2) FROM t1 WHERE c1 <= 10;
假设(c1, c2)是覆盖索引。使用这个索引,必须扫描所有c1 <= 10的行以找到最小的c2值。相比之下,考虑这个查询:
sql
SELECT MIN(c2) FROM t1 WHERE c1 = 10;
在这种情况下,具有c1 = 10的第一个索引行包含最小的c2值。只需读取一行即可产生返回的行。
对于按表维护精确行计数的存储引擎(如MyISAM,但不是InnoDB),如果WHERE子句缺失或始终为真,并且没有GROUP BY子句,这个Extra值可能出现在COUNT(*)查询中。(这是隐式分组查询的一个实例,其中存储引擎影响是否可以读取确定数量的行。)
Skip_open_table, Open_frm_only, Open_full_table (JSON属性: message)
这些值表示适用于INFORMATION_SCHEMA表查询的文件打开优化。
Skip_open_table:不需要打开表文件。信息已经从数据字典中获得。
Open_frm_only:只需读取数据字典中的表信息。
Open_full_table:未优化的信息查找。表信息必须从数据字典读取,并通过读取表文件获得。
Start temporary, End temporary (JSON属性: message)
这表示半连接的Duplicate Weedout策略使用了临时表。
unique row not found (JSON属性: message)
对于像SELECT ... FROM tbl_name这样的查询,在表上没有行满足UNIQUE索引或PRIMARY KEY的条件。
Using filesort (JSON属性: using_filesort)
MySQL必须进行额外的传递以找出如何按排序顺序检索行。排序是通过根据连接类型遍历所有行并存储匹配WHERE子句的所有行的排序键和行指针来完成的。然后对键进行排序,按排序顺序检索行。
Using index (JSON属性: using_index)
列信息是通过仅使用索引树中的信息而不必进行额外的查找来读取实际行来检索的。当查询仅使用属于单个索引的列时,可以使用这种策略。
对于具有用户定义的聚簇索引的InnoDB表,即使在Extra列中缺少Using index,也可以使用该索引。如果type是index且key是PRIMARY,就是这种情况。
有关使用的任何覆盖索引的信息显示在EXPLAIN FORMAT=TRADITIONAL和EXPLAIN FORMAT=JSON中。从MySQL 8.0.27开始,它也显示在EXPLAIN FORMAT=TREE中。
Using index condition (JSON属性: using_index_condition)
表通过访问索引元组并首先测试它们来读取,以确定是否需要读取完整的表行。通过这种方式,索引信息被用来推迟("下推")读取完整的表行,除非有必要。
Using index for group-by (JSON属性: using_index_for_group_by)
类似于Using index表访问方法,Using index for group-by表示MySQL找到了一个索引,可以用来检索GROUP BY或DISTINCT查询的所有列,而不需要对实际表进行额外的磁盘访问。此外,索引以最有效的方式使用,因此对于每个组,只读取几个索引条目。
Using index for skip scan (JSON属性: using_index_for_skip_scan)
表示使用了Skip Scan访问方法。请参见Skip Scan范围访问方法。
Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access), Using join buffer (hash join) (JSON属性: using_join_buffer)
早期连接的表以部分方式读入连接缓冲区,然后从缓冲区中使用它们的行与当前表执行连接。(Block Nested Loop)表示使用了Block Nested-Loop算法,(Batched Key Access)表示使用了Batched Key Access算法,(hash join)表示使用了哈希连接。也就是说,前一行的EXPLAIN输出中的表的键被缓冲,匹配的行以批次从由Using join buffer行表示的表中获取。
在JSON格式的输出中,using_join_buffer的值始终是Block Nested Loop、Batched Key Access或hash join之一。
从MySQL 8.0.18开始提供哈希连接;在MySQL 8.0.20或更高版本的MySQL发布中不使用Block Nested-Loop算法。
Using MRR (JSON属性: message)
表使用多范围读取优化策略进行读取。
Using sort_union(...), Using union(...), Using intersect(...) (JSON属性: message)
这些指示了index_merge连接类型的特定算法,显示了如何合并索引扫描。请参见第10.2.1.3节"索引合并优化"。
Using temporary (JSON属性: using_temporary_table)
为了解析查询,MySQL需要创建一个临时表来保存结果。这通常发生在查询包含GROUP BY和ORDER BY子句,并且列出的列不同。
Using where (JSON属性: attached_condition)
WHERE子句被用来限制哪些行与下一个表匹配或发送给客户端。除非您特意打算从表中获取或检查所有行,否则如果Extra值不是Using where且表的连接类型是ALL或index,则您的查询可能存在问题。
Using where在JSON格式的输出中没有直接对应物;attached_condition属性包含任何使用的WHERE条件。
Using where with pushed condition (JSON属性: message)
此项仅适用于NDB表。它意味着NDB集群正在使用条件下推优化来提高直接比较非索引列和常量的效率。在这种情况下,条件被"下推"到集群的数据节点,并在所有数据节点上同时评估。这消除了在网络上发送不匹配的行的需要,并且可以使这种查询的速度比不使用条件下推的情况快5到10倍。
Zero limit (JSON属性: message)
查询有一个LIMIT 0子句,不能选择任何行。