EXPLAIN
语句提供有关 MySQL 如何执行语句的信息。EXPLAIN
适用于 SELECT
、DELETE
、INSERT
、REPLACE
和 UPDATE
语句。
EXPLAIN
为 SELECT
语句中使用的每个表返回一行信息。它在输出中按 MySQL 处理语句时读取表的顺序列出表。这意味着 MySQL 从第一个表中读取一行,然后在第二个表中找到匹配的行,然后在第三个表中找到匹配的行,依此类推。当处理完所有表后,MySQL 输出选定的列并回溯表列表,直到找到一个还有更多匹配行的表。从该表中读取下一行,并继续处理下一个表。
MySQL Workbench 具有可视化解释功能,可以直观地表示 EXPLAIN
输出。请参阅 教程:使用 Explain 提高查询性能。
本节介绍 EXPLAIN
生成的输出列。后面的章节将提供有关 type
和 Extra
列的更多信息。
EXPLAIN
的每一行输出都提供有关一个表的信息。每行都包含 表 10.1,“EXPLAIN 输出列” 中汇总的值,并在该表之后进行更详细的描述。列名显示在表的第一列;第二列提供了使用 FORMAT=JSON
时输出中显示的等效属性名称。
表 10.1 EXPLAIN 输出列
列 | JSON 名称 | 含义 |
---|---|---|
id |
select_id |
SELECT 标识符 |
select_type |
无 | 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 |
无 | 附加信息 |
JSON 格式的 EXPLAIN
输出中不会显示 NULL
的 JSON 属性。
SELECT
标识符。这是查询中SELECT
的序号。如果该行引用其他行的并集结果,则该值可以是NULL
。在这种情况下,table
列会显示类似<union
的值,以指示该行引用M
,N
>id
值为M
和N
的行的并集。SELECT
的类型,可以是下表中显示的任何一种。JSON 格式的EXPLAIN
会将SELECT
类型作为query_block
的一个属性公开,除非它是SIMPLE
或PRIMARY
。表中还显示了 JSON 名称(如果适用)。select_type
值JSON 名称 含义 SIMPLE
无 简单的 SELECT
(不使用UNION
或子查询)PRIMARY
无 最外层的 SELECT
UNION
无 UNION
中的第二个或后续SELECT
语句DEPENDENT UNION
dependent
(true
)UNION
中的第二个或后续SELECT
语句,依赖于外部查询UNION RESULT
union_result
UNION
的结果。SUBQUERY
无 子查询中的第一个 SELECT
DEPENDENT SUBQUERY
dependent
(true
)子查询中的第一个 SELECT
,依赖于外部查询DERIVED
无 派生表 DEPENDENT DERIVED
dependent
(true
)依赖于另一个表的派生表 MATERIALIZED
materialized_from_subquery
物化子查询 UNCACHEABLE SUBQUERY
cacheable
(false
)一个子查询,其结果无法缓存,并且必须针对外部查询的每一行重新评估 UNCACHEABLE UNION
cacheable
(false
)UNION
中的第二个或后续 select,属于不可缓存的子查询(请参阅UNCACHEABLE SUBQUERY
)DEPENDENT
通常表示使用相关子查询。请参阅 第 15.2.15.7 节“相关子查询”。DEPENDENT SUBQUERY
的评估与UNCACHEABLE SUBQUERY
的评估不同。对于DEPENDENT SUBQUERY
,子查询仅针对其外部上下文中变量的每组不同值重新评估一次。对于UNCACHEABLE SUBQUERY
,子查询针对外部上下文的每一行重新评估。当您在
EXPLAIN
中指定FORMAT=JSON
时,输出没有与select_type
直接等效的单个属性;query_block
属性对应于给定的SELECT
。与大多数刚刚显示的SELECT
子查询类型等效的属性可用(例如,MATERIALIZED
的materialized_from_subquery
),并在适当时显示。没有与SIMPLE
或PRIMARY
等效的 JSON。非
SELECT
语句的select_type
值显示受影响表的语句类型。例如,对于DELETE
语句,select_type
为DELETE
。输出行所指表的名称。这也可以是以下值之一
<union
:该行指的是M
,N
>id
值为M
和N
的行的并集。<derived
:该行指的是N
>id
值为N
的行的派生表结果。例如,派生表可能是FROM
子句中的子查询的结果。<subquery
:该行指的是N
>id
值为N
的行的物化子查询的结果。请参阅 第 10.2.2.2 节“使用物化优化子查询”。
partitions
(JSON 名称:partitions
)查询将从中匹配记录的分区。对于非分区表,该值为
NULL
。请参阅 第 26.3.5 节“获取有关分区的信息”。连接类型。有关不同类型的描述,请参阅
EXPLAIN
连接类型。possible_keys
(JSON 名称:possible_keys
)possible_keys
列指示 MySQL 可以从中选择以查找此表中的行的索引。请注意,此列完全独立于EXPLAIN
输出中显示的表顺序。这意味着possible_keys
中的某些键在实践中可能无法与生成的表顺序一起使用。如果此列为
NULL
(或在 JSON 格式的输出中未定义),则表示没有相关的索引。在这种情况下,您可以通过检查WHERE
子句以查看它是否引用了适合索引的某些列来提高查询的性能。如果是这样,请创建一个适当的索引,并使用EXPLAIN
再次检查查询。请参阅 第 15.1.9 节“ALTER TABLE 语句”。要查看表有哪些索引,请使用
SHOW INDEX FROM
。tbl_name
key
列指示 MySQL 实际决定使用的键(索引)。如果 MySQL 决定使用possible_keys
索引之一来查找行,则该索引将列为键值。key
可能命名一个不存在于possible_keys
值中的索引。如果possible_keys
索引都不适合查找行,但查询选择的所有列都是某个其他索引的列,则可能会发生这种情况。也就是说,命名的索引覆盖了选定的列,因此,尽管它不用于确定要检索哪些行,但索引扫描比数据行扫描更有效。对于
InnoDB
,即使查询还选择了主键,辅助索引也可能覆盖选定的列,因为InnoDB
将主键值存储在每个辅助索引中。如果key
为NULL
,则 MySQL 找不到用于更有效地执行查询的索引。要强制 MySQL 使用或忽略
possible_keys
列中列出的索引,请在查询中使用FORCE INDEX
、USE INDEX
或IGNORE INDEX
。请参阅 第 10.9.4 节“索引提示”。对于
MyISAM
表,运行ANALYZE TABLE
有助于优化器选择更好的索引。对于MyISAM
表,myisamchk --analyze 具有相同的作用。请参阅 第 15.7.3.1 节“ANALYZE TABLE 语句” 和 第 9.6 节“MyISAM 表维护和崩溃恢复”。key_len
列指示 MySQL 决定使用的键的长度。key_len
的值使您能够确定 MySQL 实际使用了多部分键的多少部分。如果key
列显示NULL
,则key_len
列也显示NULL
。由于键的存储格式,对于可以为
NULL
的列,键的长度比NOT NULL
列的键长度大 1。ref
列显示哪些列或常量与key
列中命名的索引进行比较,以从表中选择行。如果值为
func
,则使用的值是某些函数的结果。要查看是哪个函数,请在EXPLAIN
之后使用SHOW WARNINGS
来查看扩展的EXPLAIN
输出。该函数实际上可能是一个运算符,例如算术运算符。rows
列指示 MySQL 认为必须检查以执行查询的行数。对于
InnoDB
表,此数字是一个估计值,可能并不总是准确的。filtered
列指示由表条件过滤的表行的估计百分比。最大值为 100,这意味着没有过滤任何行。从 100 开始递减的值表示过滤量增加。rows
显示检查的估计行数,rows
×filtered
显示与下一张表连接的行数。例如,如果rows
为 1000 且filtered
为 50.00 (50%),则要与下一张表连接的行数为 1000 × 50% = 500。此列包含有关 MySQL 如何解析查询的附加信息。有关不同值的描述,请参阅
EXPLAIN
额外信息。没有与
Extra
列对应的单个 JSON 属性;但是,可以在此列中出现的值将作为 JSON 属性或message
属性的文本公开。
EXPLAIN
输出的 type
列描述了表的连接方式。在 JSON 格式的输出中,这些值作为 access_type
属性的值找到。以下列表描述了连接类型,从最佳类型到最差类型排序
该表只有一行(= 系统表)。这是
const
连接类型的一种特殊情况。该表最多有一个匹配行,该行在查询开始时读取。因为只有一行,所以此行中列的值可以被优化器的其余部分视为常量。
const
表非常快,因为它们只读取一次。当您将
PRIMARY KEY
或UNIQUE
索引的所有部分与常量值进行比较时,将使用const
。在以下查询中,tbl_name
可以用作const
表SELECT * FROM tbl_name WHERE primary_key=1; SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2;
对于先前表中每行的组合,将从此表中读取一行。除了
system
和const
类型之外,这是最佳连接类型。当索引的所有部分都被连接使用并且索引是PRIMARY KEY
或UNIQUE NOT NULL
索引时,将使用它。eq_ref
可用于使用=
运算符进行比较的索引列。比较值可以是常量,也可以是使用从此表之前读取的表中的列的表达式。在以下示例中,MySQL 可以使用eq_ref
连接来处理ref_table
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;
对于先前表中每行的组合,将从此表中读取所有具有匹配索引值的。如果连接仅使用键的最左侧前缀,或者键不是
PRIMARY KEY
或UNIQUE
索引(换句话说,如果连接无法根据键值选择单行),则使用ref
。如果使用的键仅与几行匹配,则这是一种很好的连接类型。ref
可用于使用=
或<=>
运算符进行比较的索引列。在以下示例中,MySQL 可以使用ref
连接来处理ref_table
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
索引执行连接。此连接类型类似于
ref
,但 MySQL 会额外搜索包含NULL
值的行。此连接类型优化最常用于解析子查询。在以下示例中,MySQL 可以使用ref_or_null
连接来处理ref_table
SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
此连接类型表示使用了索引合并优化。在这种情况下,输出行中的
key
列包含使用的索引列表,key_len
包含使用的索引的最长键部分列表。有关更多信息,请参阅 第 10.2.1.3 节“索引合并优化”。此类型替换了以下形式的某些
IN
子查询的eq_ref
value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery
只是一个索引查找函数,它完全替换了子查询以提高效率。此连接类型类似于
unique_subquery
。它替换了IN
子查询,但它适用于以下形式的子查询中的非唯一索引value IN (SELECT key_column FROM single_table WHERE some_expr)
仅检索给定范围内的行,使用索引来选择行。输出行中的
key
列指示使用了哪个索引。key_len
包含使用的最长键部分。ref
列对于此类型为NULL
。当使用
=
、<>
、>
、>=
、<
、<=
、IS NULL
、<=>
、BETWEEN
、LIKE
或IN()
运算符将键列与常量进行比较时,可以使用range
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
连接类型与ALL
相同,只是扫描了索引树。这以两种方式发生如果索引是查询的覆盖索引,并且可以用来满足表中所需的所有数据,则只扫描索引树。在这种情况下,
Extra
列显示Using index
。索引仅扫描通常比ALL
快,因为索引的大小通常小于表数据。使用从索引读取以按索引顺序查找数据行来执行完整表扫描。
Uses index
不会出现在Extra
列中。
当查询仅使用属于单个索引的列时,MySQL 可以使用此连接类型。
对先前表中每行的组合执行完整表扫描。如果该表是第一个未标记为
const
的表,这通常不好,并且在所有其他情况下通常非常糟糕。通常,您可以通过添加索引来避免ALL
,这些索引允许根据常量值或先前表中的列值从表中检索行。
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
一起显示。有关更多信息,请参阅 第 10.3.13 节“降序索引”。Child of '
(JSON:table
' pushed join@1message
文本)此表在可以下推到 NDB 内核的连接中被引用为
table
的子级。仅适用于 NDB 集群,当启用下推连接时。有关更多信息和示例,请参阅ndb_join_pushdown
服务器系统变量的说明。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(
(JSON 属性:tbl_name
)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
)LooseScan(
(JSON 属性:m
..n
)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
条件匹配的一行后,不会检查该表中与前一行组合匹配的更多行。以下是可通过这种方式优化的查询类型的示例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
中的每一行,无论t2
中实际匹配多少行,MySQL 只需要在t2
中执行一次查找。这还可以表明,
WHERE
条件形式为NOT IN (
或子查询
)NOT EXISTS (
已在内部转换为反连接。这将删除子查询并将其表引入最顶层查询的计划中,从而改进成本计划。通过合并半连接和反连接,优化器可以更自由地重新排序执行计划中的表,在某些情况下可以生成更快的计划。子查询
)您可以通过检查执行
EXPLAIN
后SHOW WARNINGS
中的Message
列或EXPLAIN FORMAT=TREE
的输出,来查看何时对给定查询执行反连接转换。注意反连接是半连接
的补充。反连接返回表_a
JOIN表_b
ON条件
表_a
中所有在表_b
中没有匹配条件
的行的行。计划尚未准备好
(JSON 属性:无)当优化器尚未完成为在命名连接中执行的语句创建执行计划时,此值将与
EXPLAIN FOR CONNECTION
一起出现。如果执行计划输出包含多行,则根据优化器确定完整执行计划的进度,其中任何一行或所有行都可能具有此Extra
值。已为每条记录检查范围(索引映射:
(JSON 属性:N
)message
)MySQL 没有找到要使用的良好索引,但发现知道前面表中的列值后,可能会使用某些索引。对于前面表中的每个行组合,MySQL 都会检查是否可以使用
range
或index_merge
访问方法来检索行。这并不是很快,但比完全不使用索引执行连接要快。适用性标准如 第 10.2.1.2 节“范围优化”和 第 10.2.1.3 节“索引合并优化”中所述,但前面表的所有列值都是已知的并被视为常量。索引从 1 开始编号,顺序与
SHOW INDEX
为表显示的顺序相同。索引映射值N
是一个位掩码值,指示哪些索引是候选索引。例如,值0x19
(二进制 11001)表示索引 1、4 和 5 被考虑。递归
(JSON 属性:recursive
)这表示该行适用于递归
SELECT
部分的递归公用表表达式。请参阅 第 15.2.20 节“WITH(公用表表达式)”。重新物化
(JSON 属性:rematerialize
)重新物化 (X,...)
显示在表T
的EXPLAIN
行中,其中X
是任何在读取T
的新行时触发其重新物化的横向派生表。例如SELECT ... FROM t, LATERAL (derived table that refers to t) AS dt ...
每次顶级查询处理
t
的新行时,都会重新物化派生表的内容以使其保持最新。已扫描
(JSON 属性:N
个数据库message
)这指示服务器在处理
INFORMATION_SCHEMA
表的查询时执行了多少次目录扫描,如 第 10.2.3 节“优化 INFORMATION_SCHEMA 查询” 中所述。N
的值可以是 0、1 或all
。选择表已优化掉
(JSON 属性:message
)优化器确定 1) 最多应返回一行,以及 2) 要生成此行,必须读取一组确定性行。当可以在优化阶段读取要读取的行时(例如,通过读取索引行),则在查询执行期间无需读取任何表。
当查询被隐式分组(包含聚合函数但没有
GROUP BY
子句)时,将满足第一个条件。当每个使用的索引执行一行查找时,将满足第二个条件。读取的索引数决定了要读取的行数。请考虑以下隐式分组查询
SELECT MIN(c1), MIN(c2) FROM t1;
假设可以通过读取一行索引行来检索
MIN(c1)
,并且可以通过从不同索引读取一行来检索MIN(c2)
。也就是说,对于每列c1
和c2
,都存在一个索引,其中该列是索引的第一列。在这种情况下,将返回一行,该行是通过读取两行确定性行生成的。如果要读取的行不是确定性的,则不会出现此
Extra
值。请考虑此查询SELECT MIN(c2) FROM t1 WHERE c1 <= 10;
假设
(c1, c2)
是覆盖索引。使用此索引,必须扫描所有c1 <= 10
的行以找到最小c2
值。相比之下,请考虑此查询SELECT MIN(c2) FROM t1 WHERE c1 = 10;
在这种情况下,
c1 = 10
的第一行索引行包含最小c2
值。只需读取一行即可生成返回的行。对于每个表维护精确行数的存储引擎(例如
MyISAM
,但不是InnoDB
),此Extra
值可能会出现在COUNT(*)
查询中,其中WHERE
子句丢失或始终为 true 并且没有GROUP BY
子句。(这是隐式分组查询的一个实例,其中存储引擎会影响是否可以读取确定数量的行。)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
)这表示半连接重复数据消除策略使用临时表。
unique row not found
(JSON 属性:message
)对于
SELECT ... FROM
等查询,没有行满足表上的tbl_name
UNIQUE
索引或PRIMARY KEY
的条件。Using filesort
(JSON 属性:using_filesort
)MySQL 必须执行额外的遍历才能找出如何按排序顺序检索行。排序是通过根据连接类型遍历所有行并存储与
WHERE
子句匹配的所有行的排序关键字和指向该行的指针来完成的。然后对关键字进行排序,并按排序顺序检索行。请参阅 第 10.2.1.16 节“ORDER BY 优化”。Using index
(JSON 属性:using_index
)仅使用索引树中的信息即可从表中检索列信息,而无需执行额外的查找来读取实际行。当查询仅使用属于单个索引的列时,可以使用此策略。
对于具有用户定义的聚集索引的
InnoDB
表,即使Using index
不在Extra
列中,也可以使用该索引。如果type
为index
并且key
为PRIMARY
,则会出现这种情况。有关使用的任何覆盖索引的信息都显示在
EXPLAIN FORMAT=TRADITIONAL
和EXPLAIN FORMAT=JSON
中。它也显示在EXPLAIN FORMAT=TREE
中。Using index condition
(JSON 属性:using_index_condition
)通过访问索引元组并首先对其进行测试以确定是否读取完整的表行来读取表。通过这种方式,使用索引信息来延迟(“下推”)读取完整的表行,除非有必要。请参阅 第 10.2.1.6 节“索引条件下推优化”。
Using index for group-by
(JSON 属性:using_index_for_group_by
)与
Using index
表访问方法类似,Using index for group-by
表示 MySQL 找到了一个索引,该索引可用于检索GROUP BY
或DISTINCT
查询的所有列,而无需对实际表进行任何额外的磁盘访问。此外,以最有效的方式使用索引,以便对于每个组,只读取几个索引条目。有关详细信息,请参阅 第 10.2.1.17 节“GROUP BY 优化”。Using index for skip scan
(JSON 属性:using_index_for_skip_scan
)表示使用了跳跃扫描访问方法。请参阅 跳跃扫描范围访问方法。
Using join buffer (Block Nested Loop)
、Using join buffer (Batched Key Access)
、Using join buffer (hash join)
(JSON 属性:using_join_buffer
)来自先前连接的表的部分内容被读入连接缓冲区,然后使用缓冲区中的行与当前表执行连接。
(块嵌套循环)
表示使用块嵌套循环算法,(批量键访问)
表示使用批量键访问算法,而(哈希连接)
表示使用哈希连接。也就是说,来自EXPLAIN
输出的上一行的表的键被缓冲,并且匹配的行从使用连接缓冲区
出现的行所表示的表中批量获取。在 JSON 格式的输出中,
using_join_buffer
的值始终是块嵌套循环
、批量键访问
或哈希连接
之一。有关哈希连接的更多信息,请参见第 10.2.1.4 节“哈希连接优化”。
有关批量键访问算法的信息,请参见批量键访问连接。
使用 MRR
(JSON 属性:message
)使用多范围读取优化策略读取表。请参见第 10.2.1.11 节“多范围读取优化”。
使用 sort_union(...)
、使用 union(...)
、使用 intersect(...)
(JSON 属性:message
)这些指示了显示如何为
index_merge
连接类型合并索引扫描的特定算法。请参见第 10.2.1.3 节“索引合并优化”。使用临时表
(JSON 属性:using_temporary_table
)为了解析查询,MySQL 需要创建一个临时表来保存结果。如果查询包含列出不同列的
GROUP BY
和ORDER BY
子句,则通常会发生这种情况。使用 where
(JSON 属性:attached_condition
)使用
WHERE
子句来限制哪些行与下一个表匹配或发送到客户端。除非您特别打算从表中获取或检查所有行,否则如果Extra
值不是使用 where
并且表连接类型是ALL
或index
,则您的查询中可能存在错误。使用 where
在 JSON 格式的输出中没有直接对应的值;attached_condition
属性包含使用的任何WHERE
条件。使用带有下推条件的 where
(JSON 属性:message
)此项仅适用于
NDB
表。这意味着 NDB 集群正在使用条件下推优化来提高非索引列和常量之间直接比较的效率。在这种情况下,条件被“下推”到集群的数据节点,并在所有数据节点上同时进行评估。这消除了通过网络发送不匹配行的需要,并且可以使此类查询的速度比可以使用但未使用条件下推的情况快 5 到 10 倍。有关更多信息,请参见第 10.2.1.5 节“引擎条件下推优化”。零限制
(JSON 属性:message
)查询具有
LIMIT 0
子句,并且不能选择任何行。
您可以通过将EXPLAIN
输出的rows
列中的值相乘来很好地指示连接的好坏。这应该大致告诉您 MySQL 必须检查多少行才能执行查询。如果您使用max_join_size
系统变量限制查询,则此行积也将用于确定要执行哪些多表SELECT
语句以及要中止哪些语句。请参见第 7.1.1 节“配置服务器”。
以下示例显示了如何根据EXPLAIN
提供的信息逐步优化多表连接。
假设您有如下所示的SELECT
语句,并且您计划使用EXPLAIN
来检查它
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn,
tt.ProjectReference, tt.EstimatedShipDate,
tt.ActualShipDate, tt.ClientID,
tt.ServiceCodes, tt.RepetitiveID,
tt.CurrentProcess, tt.CurrentDPPerson,
tt.RecordVolume, tt.DPPrinted, et.COUNTRY,
et_1.COUNTRY, do.CUSTNAME
FROM tt, et, et AS et_1, do
WHERE tt.SubmitTime IS NULL
AND tt.ActualPC = et.EMPLOYID
AND tt.AssignedPC = et_1.EMPLOYID
AND tt.ClientID = do.CUSTNMBR;
对于此示例,请做出以下假设
要比较的列已声明如下。
表 列 数据类型 tt
ActualPC
CHAR(10)
tt
AssignedPC
CHAR(10)
tt
ClientID
CHAR(10)
et
EMPLOYID
CHAR(15)
do
CUSTNMBR
CHAR(15)
这些表具有以下索引。
表 索引 tt
ActualPC
tt
AssignedPC
tt
ClientID
et
EMPLOYID
(主键)do
CUSTNMBR
(主键)tt.ActualPC
值不是均匀分布的。
最初,在执行任何优化之前,EXPLAIN
语句会生成以下信息
table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
do ALL PRIMARY NULL NULL NULL 2135
et_1 ALL PRIMARY NULL NULL NULL 74
tt ALL AssignedPC, NULL NULL NULL 3872
ClientID,
ActualPC
Range checked for each record (index map: 0x23)
因为每个表的type
都是ALL
,所以此输出表明 MySQL 正在生成所有表的笛卡尔积;也就是说,每一行的组合。这需要相当长的时间,因为必须检查每个表中行数的乘积。对于当前的情况,这个乘积是 74 × 2135 × 74 × 3872 = 45,268,558,720 行。如果表更大,您可以想象它将花费多长时间。
这里的一个问题是,如果将列声明为相同的类型和大小,则 MySQL 可以更有效地使用列上的索引。在这种情况下,如果将VARCHAR
和CHAR
声明为相同的大小,则它们被认为是相同的。tt.ActualPC
被声明为CHAR(10)
,而et.EMPLOYID
是CHAR(15)
,因此存在长度不匹配。
要解决列长度之间的这种差异,请使用ALTER TABLE
将ActualPC
的长度从 10 个字符增加到 15 个字符
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
现在tt.ActualPC
和et.EMPLOYID
都是VARCHAR(15)
。再次执行EXPLAIN
语句会产生以下结果
table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC, NULL NULL NULL 3872 Using
ClientID, where
ActualPC
do ALL PRIMARY NULL NULL NULL 2135
Range checked for each record (index map: 0x1)
et_1 ALL PRIMARY NULL NULL NULL 74
Range checked for each record (index map: 0x1)
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
这并不完美,但要好得多:rows
值的乘积减少了 74 倍。此版本在几秒钟内执行完毕。
可以进行第二次更改,以消除tt.AssignedPC = et_1.EMPLOYID
和tt.ClientID = do.CUSTNMBR
比较的列长度不匹配
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
MODIFY ClientID VARCHAR(15);
修改后,EXPLAIN
会生成如下所示的输出
table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using
ClientID, where
ActualPC
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
此时,查询几乎已优化到最佳状态。剩下的问题是,默认情况下,MySQL 假设tt.ActualPC
列中的值是均匀分布的,而tt
表的情况并非如此。幸运的是,很容易告诉 MySQL 分析键分布
mysql> ANALYZE TABLE tt;
使用附加的索引信息,连接是完美的,EXPLAIN
会生成以下结果
table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC NULL NULL NULL 3872 Using
ClientID, where
ActualPC
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
EXPLAIN
输出中的rows
列是 MySQL 连接优化器的 educated guess。通过将rows
乘积与查询返回的实际行数进行比较,检查这些数字是否接近真实值。如果数字相差很大,则您可能会通过在SELECT
语句中使用STRAIGHT_JOIN
并尝试以不同的顺序在FROM
子句中列出表来获得更好的性能。(但是,STRAIGHT_JOIN
可能会阻止使用索引,因为它禁用了半连接转换。请参见使用半连接转换优化 IN 和 EXISTS 子查询谓词。)
在某些情况下,当将EXPLAIN SELECT
与子查询一起使用时,可以执行修改数据的语句;有关更多信息,请参见第 15.2.15.8 节“派生表”。