10.8.2 EXPLAIN 输出格式
EXPLAIN
语句提供了 MySQL 执行语句的信息。EXPLAIN
可以与SELECT
、DELETE
、INSERT
、REPLACE
和UPDATE
语句一起使用。
EXPLAIN
返回了 SELECT 语句中每个表的信息。它将输出结果中的表按 MySQL 处理语句时读取它们的顺序列出。这意味着 MySQL 首先读取第一个表,然后在第二个表中找到匹配行,接着第三个表,直到所有表都处理完毕。然后,MySQL 输出选择的列,并从最后一个表回溯,直到找到有更多匹配行的表。然后继续读取下一个表。
MySQL Workbench 提供了可视化的 Explain 能力,提供了EXPLAIN
输出的可视化表示。请参阅使用 Explain 提高查询性能教程。
本节描述了EXPLAIN
的输出列。后续部分提供了关于type
和Extra
列的其他信息。
每个EXPLAIN
输出行都提供了一个表的信息。每一行包含第10.1节,“EXPLAIN 输出列”中总结的值,详细描述在该表后面。列名显示在表的第一列;第二列提供了当使用FORMAT=JSON
时的等价属性名称。
第10.1节 EXPLAIN 输出列
Column | JSON Name | Meaning |
---|---|---|
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 属性如果是 NULL
则不在 JSON 格式的 EXPLAIN
输出中显示。
-
SELECT 标识符。这个是查询中的顺序号码。如果该行引用了其他行的 union 结果,那么该行的
table
列将显示一个值,如<union
,表示该行引用了 id 值为M
,N
>M
和N
的行。 -
SELECT 类型,可以是以下表格中显示的任何类型。 JSON 格式的
EXPLAIN
将SELECT
类型作为query_block
的属性,除非是SIMPLE
或PRIMARY
。JSON 名称(如果适用)也在表格中显示。select_type
ValueJSON Name Meaning SIMPLE
简单的 SELECT
(不使用UNION
或子查询)PRIMARY
None SELECT
UNION
None SELECT
语句在UNION
中的第二或后续语句DEPENDENT UNION
dependent
(true
)SELECT
语句在UNION
中的第二或后续语句,依赖于外层查询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
可缓存
(false
)UNION 中的第二个或后续 select,属于不可缓存子查询(见 不可缓存子查询
)DEPENDENT
通常表示相关子查询。见第15.2.15.7节,“相关子查询”。DEPENDENT 子查询
的评估方式不同于不可缓存子查询
。对于DEPENDENT 子查询
,子查询只对每个不同的外部上下文变量重新评估;对于不可缓存子查询
,子查询对每行外部上下文重新评估。当您使用
FORMAT=JSON
和EXPLAIN
时,输出没有直接等价于select_type
的单个属性;query_block
属性对应于给定的SELECT
。大多数SELECT
子查询类型的属性都可用(例如,materialized_from_subquery
对应于MATERIALIZED
),并在适当时显示;没有 JSON 等价于SIMPLE
或PRIMARY
的。非
SELECT
语句的select_type
值显示影响的表类型。例如,select_type
是DELETE
对于DELETE
语句。 -
输出行所指的表名。这也可以是以下值之一:
-
<union
:输出行指向 id 值为M
,N
>M
和N
的行。 -
<derived
:输出行指向 id 值为N
>N
的派生表结果。派生表可能来自于FROM 子句 中的子查询。 -
<subquery
:输出行指向 id 值为N
>N
的物化子查询。请参阅第10.2.2.2节,“使用物化子查询优化”。
-
-
partitions
(JSON名称:partitions
)查询将匹配的分区。该值为非分区表时为
NULL
。请参阅第26.3.5节,“获取关于分区的信息”。 -
连接类型。有关不同类型的描述,请参阅
EXPLAIN
连接类型。 -
possible_keys
(JSON名称: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 Statement”,和第9.6节,“MyISAM Table Maintenance and Crash Recovery”。 -
该
key_len
列指示MySQL实际使用的键的长度。key_len
值使您可以确定多部分键中MySQL实际使用的部分。如果key
列显示NULL
,那么key_len
列也将显示NULL
。由于键存储格式,可能为
NULL
的列的键长度比不可能为NULL
的列长一个。 -
该
ref
列显示了哪些列或常量与索引名在key
列中比较以从表中选择行。如果值是
func
,则使用的值是某个函数的结果。要知道哪个函数,请使用SHOW WARNINGS
,在EXPLAIN
后面,以获取扩展的EXPLAIN
输出。该函数可能是一个算术运算符。 -
rows
列指示 MySQL 必须检查的行数以执行查询。对于
InnoDB
表,这个数字是一个估计值,可能不总是准确的。 -
filtered
列指示估算的表行数被过滤的百分比。最大值是 100,表示没有对行进行过滤。从 100 值递减的数字表示增加的过滤量。rows
显示检查的行数,rows
×filtered
显示与下一个表进行连接的行数。例如,如果rows
是 1000 和filtered
是 50.00(50%),则需要与下一个表进行连接的行数是 1000 × 50% = 500。 -
这个列包含了 MySQL 解析查询的额外信息。关于不同值的描述,请参见
EXPLAIN
Extra Information。没有单个 JSON 属性对应于
Extra
列;然而,这些值可以作为 JSON 属性,或者是message
属性的文本。
type
列中的EXPLAIN
输出描述了表的连接方式。在 JSON 格式输出中,这些值是access_type
属性的值。以下列表描述了连接类型,从最好到最差:
-
该表只有一个行(= 系统表)。这是一种特殊情况的
const
连接类型。 -
该表最多只有一个匹配行,这个行在查询开始时被读取。因为只有一行,优化器可以将这个列中的值视为常量。
const
表非常快,因为它们只读取一次。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;
-
从这个表中读取所有与索引值匹配的行,每个组合的行来自前一个表。
ref
如果连接只使用索引的左前缀或索引不是PRIMARY KEY
或UNIQUE
索引(换言之,如果连接不能根据键值选择单个行)。如果用到的键匹配的行很少,这是一个好的连接类型。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节,“索引合并优化”。 -
这个类型替代了
eq_ref
,用于某些以下形式的IN子查询: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
,因为这个类型。range
可以在使用等于、不等于、大于、小于、大于等于、小于等于、IS NULL、LIKE 或 IN() 操作符比较键列与常量时使用: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);
-
索引连接类型与
ALL
相同,但索引树被扫描。这有两种情况:-
如果索引是查询的覆盖索引,可以用来满足所有表数据,仅扫描索引树。在这种情况下,
Extra
列显示Using index
。索引只读取通常比ALL
快,因为索引通常小于表数据。 -
使用索引来查找数据行,进行全表扫描。
Uses index
不出现在Extra
列中。
MySQL 可以在查询只使用单个索引的列时使用这个连接类型。
-
-
对每个前一个表的行组合进行全表扫描。这通常在第一个表不标记
const
时不好,通常在所有其他情况下都很糟。通常,可以通过添加使表基于常量值或前一个表的列值来避免ALL
。
Extra
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 节,“降序索引”。 -
'
(JSON:table
' 的子表@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
)支持一个 handler 方法,快速删除所有表格行。这Extra
值在引擎使用这个优化时显示。 -
Distinct
(JSON 属性:distinct
MySQL 正在寻找唯一值,所以它停止搜索当前行组合的更多行,直到找到第一个匹配行。
-
FirstMatch(
(JSON 属性:tbl_name
)first_match
使用 semijoin FirstMatch 连接 shortcutting 策略对
tbl_name
。 -
Full scan on NULL key
(JSON 属性:message
在子查询优化中,出现这种情况是因为优化器无法使用索引查找访问方法的fallback策略。
-
Impossible HAVING
(JSON 属性:message
)HAVING 子句总是假设为 false,无法选择任何行。
-
Impossible WHERE
(JSON 属性:message
)WHERE 子句总是假设为 false,无法选择任何行。
-
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
)对于具有连接的查询,出现了空表或满足唯一索引条件的行为空表。
-
无匹配行后分区剪枝
(JSON 属性:message
)对于
DELETE
或UPDATE
,优化器在分区剪枝后找不到要删除或更新的行。与SELECT
语句中的“不可能WHERE”类似。 -
无使用表
(JSON 属性:message
)对于
INSERT
或REPLACE
语句,EXPLAIN
在没有SELECT
部分时显示该值。例如,对于EXPLAIN INSERT INTO t VALUES(10)
,因为这等同于EXPLAIN INSERT INTO t SELECT 10 FROM DUAL
。 -
不存在
(JSON 属性:message
)MySQL 能够对查询进行
LEFT JOIN
优化,并且在找到满足LEFT JOIN
_criteria 的行后,不再检查该表中的其他行。以下是一个可以被优化的查询示例: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
中实际匹配的行数。这也可以表明一个形式为
NOT IN (
或subquery
)NOT EXISTS (
的 WHERE 条件已经被内部转换为反连接。这将移除子查询,带入顶层查询的计划,为成本规划提供改进。通过合并半连接和反连接,优化器可以更自由地重新排列执行计划,从而在某些情况下产生更快的计划。subquery
)可以通过检查
SHOW WARNINGS
中的Message
列或EXPLAIN FORMAT=TREE
的输出来查看某个查询是否执行了反连接变换。Note反连接是半连接的补充,形式为
。反连接返回table_a
JOINtable_b
ONcondition
table_a
中的所有行,其中没有在table_b
中找到匹配condition
的行。 -
Plan isn't ready yet
(JSON 属性:none)在使用
EXPLAIN FOR CONNECTION
时,优化器还没有完成对命名连接语句的执行计划创建。如果执行计划输出包含多行,那么其中任何一行或所有行都可能具有这个Extra
值,取决于优化器在确定完整执行计划时的进度。 -
Range checked for each record (index map:
(JSON 属性:N
)message
)MySQL 没有找到合适的索引,但是发现一些索引可能在前一个表中的列值被知道后可以使用。对于每个前一个表的行组合,MySQL 检查是否可以使用
range
或index_merge
访问方法来检索行。这不是很快,但是比没有索引的 join 快。可应用性标准在第10.2.1.2节,“范围优化” 和 第10.2.1.3节,“索引合并优化”,除了所有前一个表的列值都被知道且被认为是常量外。索引从 1 开始,以
SHOW INDEX
对该表的顺序相同。索引映射值N
是一个位掩码值,表示哪些索引是候选项。例如,值为0x19
(二进制 11001) 表示索引 1、4 和 5 是候选项。 -
Recursive
(JSON 属性:recursive
)这表示该行适用于递归
SELECT
部分的递归公共表达式。见第15.2.20节,“WITH (Common Table Expressions)”。 -
重新计算
(JSON 属性:rematerialize
)重新计算(X,...)
在EXPLAIN
行中显示,whereX
是任何侧向派生表,如果对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)
是一个覆盖索引。使用这个索引,所有值。相比之下,考虑这个查询: SELECT MIN(c2) FROM t1 WHERE c1 = 10;
在这种情况下,第一个索引行
值。只需要读取一行来生产返回的行。 对于维护每个表exact行数的存储引擎(例如
MyISAM
,但不是InnoDB
),这个Extra
值可以出现于缺少或总是真的WHERE子句和没有GROUP BY子句的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
-
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
(using_index_condition
)表格被读取,首先访问索引元组,然后测试它们以确定是否需要读取完整的表行。在这种方式中,索引信息用于推迟(“push down”) 读取完整表行,除非必要。请参见第10.2.1.6节,“索引条件推迟优化”。
-
Using index for group-by
(using_index_for_group_by
)类似于
使用索引
表访问方法,使用索引为group-by
指示MySQL找到一个可以用来检索所有列的GROUP BY
或DISTINCT
查询而不需要实际表的磁盘访问。另外,该索引被用于最有效地方式,以便每个组只读取少量索引条目。详见第10.2.1.17节,“GROUP BY 优化”。 -
使用索引为跳过扫描
(JSON 属性:using_index_for_skip_scan
)指示Skip Scan访问方法。详见跳过扫描范围访问方法。
-
使用连接缓冲区(Block Nested Loop)
,使用连接缓冲区(Batched Key Access)
,使用连接缓冲区(hash join)
(JSON 属性:using_join_buffer
)早期连接的表格将被读取到join缓冲区中,然后从缓冲区中使用当前表格进行连接。
(Block Nested Loop)
表示使用 Block Nested-Loop 算法,(Batched Key Access)
表示使用 Batched Key Access 算法,(hash join)
表示使用哈希连接。也就是说,前一行的表格键将被缓冲,然后从对应的表格中批量读取匹配行。在 JSON 格式输出中,
using_join_buffer
的值总是其中之一:Block Nested Loop
、Batched Key Access
或hash join
。关于哈希连接的更多信息,请见第10.2.1.4节,“哈希连接优化”。
关于 Batched Key Access 算法的信息,请见Batched Key Access Joins。
-
Using MRR
(JSON 属性:message
)使用多范围读取优化策略读取表格。见第10.2.1.11节,“多范围读取优化”。
-
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
)使用
ALL
或index
连接类型,如果Extra
值不是Using where
,可能存在查询错误。Using where
没有直接对应的 JSON 格式输出;attached_condition
属性包含任何ALL
或index
连接类型。 -
Using where with pushed condition
(JSON属性:message
)只有
NDB
表才适用。它意味着 NDB 集群使用条件下推优化来提高直接比较非索引列和常量的效率。在这种情况下,条件被“推送到”数据节点,并在所有数据节点同时评估。这消除了网络上发送不匹配行的需求,可以加速查询速度,提高5-10倍。更多信息,请参见第10.2.1.5节,“引擎条件下推优化”。 -
Zero limit
(JSON 属性:message
)该查询包含了
LIMIT 0
子句,不能选择任何行。
可以通过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;
对于这个示例,假设以下条件成立。
-
比较的列已经被声明如下。
Table Column Data Type tt
ActualPC
CHAR(10)
tt
AssignedPC
CHAR(10)
tt
ClientID
CHAR(10)
et
EMPLOYID
CHAR(15)
do
CUSTNMBR
CHAR(15)
-
这些表格拥有以下索引。
Table Index 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 = 45268558720 行。如果表格更大,你可以想象它将花多久。
一个问题是,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
这不是完美的,但是已经好了很多:行数的乘积减少了一个因子 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
rows
在EXPLAIN
的输出结果是一个MySQL连接优化器的有道理猜测。通过比较rows
乘积与实际查询返回的行数,检查这些数字是否接近真实值。如果这些数字差别很大,你可能会通过在SELECT
语句中使用STRAIGHT_JOIN
,并尝试在FROM
子句中将表的顺序改为不同的顺序。然而,STRAIGHT_JOIN
可能会禁止索引的使用,因为它禁用半连接变换。见优化IN和EXISTS子查询谓词与半连接变换。
在某些情况下,可以使用EXPLAIN SELECT
语句执行修改数据的语句;更多信息,请参见第15.2.15.8节,“派生表”。