该 EXPLAIN 语句提供了 MySQL 执行语句的信息。EXPLAIN 适用于 SELECT、DELETE、INSERT、REPLACE 和 UPDATE 语句。
EXPLAIN 返回每个表的信息行,用于在 SELECT 语句中使用的表。它按照 MySQL 读取表的顺序列出表。这样,MySQL 读取第一表的行,然后在第二表中找到匹配的行,然后在第三表中找到匹配的行,以此类推。当所有表都被处理后,MySQL 输出所选列并回溯到表列表,直到找到更多匹配行的表。然后,读取该表的下一行,并继续处理下一个表。
MySQL Workbench 具有 Visual Explain 功能,可以提供 EXPLAIN 输出的可视化表示。请参阅 Tutorial: 使用 Explain 提高查询性能。
本节描述了 EXPLAIN 生成的输出列。后续部分提供了关于 type 和 Extra 列的更多信息。
每个 EXPLAIN 输出行提供了一个表的信息。每行包含 表 10.1, “EXPLAIN 输出列” 中总结的值,并在表后面详细描述。列名显示在表的第一列;第二列提供了当 FORMAT=JSON 时在输出中显示的等效属性名称。
表 10.1 EXPLAIN 输出列
| Column | JSON Name | Meaning |
|---|---|---|
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 输出中。
-
该
SELECT标识符。这是查询中的顺序号。SELECT语句在查询中的顺序号。该值可以是NULL,如果行引用其他行的联合结果。在这种情况下,table列显示一个值,如<union,以指示该行引用了M,N>id值为M和N的行的联合。 -
该
SELECT的类型,可以是以下表格中显示的任何一种。JSON 格式的EXPLAIN将SELECT类型作为query_block的属性,除非它是SIMPLE或PRIMARY。表格中也显示了 JSON 名称(如果适用)。select_typeValueJSON Name Meaning SIMPLE无 简单的 SELECT(不使用UNION或子查询)PRIMARY无 最外层的 SELECTUNION无 第二个或后续的 SELECT语句在UNION中DEPENDENT UNIONdependent(true)第二个或后续的 SELECT语句在UNION中,依赖于外部查询UNION RESULTunion_result一个 UNION的结果。SUBQUERY无 子查询中的第一个 SELECTDEPENDENT SUBQUERYdependent(true)子查询中的第一个 SELECT,依赖于外部查询DERIVED无 派生表 DEPENDENT DERIVEDdependent(true)派生表依赖于另一个表 MATERIALIZEDmaterialized_from_subquery物化的子查询 UNCACHEABLE SUBQUERYcacheable(false)一个子查询,其结果不能被缓存,必须为外部查询的每一行重新计算 UNCACHEABLE UNIONcacheable(false)一个 UNION中的第二个或后续的选择,属于不可缓存的子查询(见UNCACHEABLE SUBQUERY)DEPENDENT通常表示相关子查询的使用。见 第 15.2.15.7 节,“相关子查询”。DEPENDENT SUBQUERY评估与UNCACHEABLE SUBQUERY评估不同。对于DEPENDENT SUBQUERY,子查询仅在外部上下文的不同值集下重新计算一次。对于UNCACHEABLE SUBQUERY,子查询将为外部上下文的每一行重新计算。当您指定
FORMAT=JSON与EXPLAIN时,输出没有单个属性直接等同于select_type;query_block属性对应于给定的SELECT。大多数SELECT子查询类型的等效属性(例如materialized_from_subquery对于MATERIALIZED)可用,并在适当时显示。没有 JSON 等效于SIMPLE或PRIMARY。该
select_type值对于非-SELECT语句显示受影响表的语句类型。例如,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)该
possible_keys列指示 MySQL 可以从中选择的索引,以查找该表中的行。请注意,该列与EXPLAIN输出中的表顺序无关。如果该列为
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 决定使用的键的长度。该值使您可以确定 MySQL 实际使用了多少个多部分键的部分。如果key列说NULL,则key_len列也说NULL。由于键存储格式,可能为 NULL 的列的键长度比 NOT NULL 列的键长度大 1。
-
ref列显示了与key列中命名的索引进行比较的列或常量,以从表中选择行。如果该值是
func,则使用的值是某些函数的结果。要查看哪个函数,请使用SHOW WARNINGSfollowingEXPLAIN查看扩展的EXPLAIN输出。该函数可能实际上是一个运算符,例如算术运算符。 -
rows列指示 MySQL 认为必须检查的行数以执行查询。对于
InnoDB表,该数字是一个估算,可能不总是准确的。 -
filtered列指示表条件过滤的估算百分比。最大值为 100,表示没有行被过滤。从 100 降低的值表示增加的行过滤。rows显示了估算的行数,而rows×filtered显示了与下一个表连接的行数。例如,如果rows是 1000,而filtered是 50.00(50%),那么与下一个表连接的行数是 1000 × 50% = 500。 -
该列包含 MySQL 解析查询的附加信息。有关不同值的描述,请参阅
EXPLAIN 附加信息。没有单个 JSON 属性对应
Extra列;然而,该列中的值将作为 JSON 属性或message属性的文本公开。
type 列描述了表如何连接。在 JSON 格式的输出中,这些是 access_type 属性的值。以下列表描述了连接类型,按照从最好到最坏的顺序:
-
该表只有一个行 (= 系统表)。这是
const连接类型的特殊情况。 -
该表最多有一行匹配的行,该行在查询开始时被读取。由于只有一个行,因此该行中的列值可以被优化器视为常量。
const表非常快,因为它们只读取一次。const用于比较所有PRIMARY KEY或UNIQUE索引的所有部分到常量值。在以下查询中,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; -
该连接类型表明 Index Merge 优化被使用。在这种情况下,输出行中的
key列包含使用的索引列表,而key_len列包含使用的索引的最长键部分。有关更多信息,请参阅第 10.2.1.3 节,“Index Merge 优化”。 -
该类型替换
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,<=>,BETWEEN,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); -
该
index连接类型与ALL相同,除了扫描索引树。这发生在两个情况下:-
如果索引是查询的覆盖索引,可以满足表中的所有数据需求,那么只扫描索引树。在这种情况下,
Extra列说Using index。索引扫描通常比ALL快,因为索引的大小通常小于表数据。 -
使用索引读取来查找表中的数据行,按照索引顺序进行全表扫描。
Uses index不会出现在Extra列中。
MySQL 可以在查询仅使用单个索引的列时使用该连接类型。
-
-
对每个表的每个组合行进行全表扫描。这通常不是好的,除非该表是第一个未标记为
const的表,否则通常非常糟糕。在大多数情况下,可以通过添加启用基于常量值或早期表中的列值的行检索来避免ALL。
EXPLAIN 输出的 Extra 列包含查询解析的附加信息。以下列表解释了该列中的值。每个项目还指示了 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 节,“Descending Indexes”。 -
子表'(JSON:table'推送连接@1message文本)该表被引用为
table的子表在一个可以推送到 NDB 内核的连接中。仅在 NDB 集群中启用推送下降连接时适用。请参阅ndb_join_pushdown服务器系统变量的描述,以获取更多信息和示例。 -
const 行未找到(JSON 属性:const_row_not_found)对于查询,如
SELECT ... FROM,表为空。tbl_name -
删除所有行(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,优化器在分区修剪后找不到要删除或更新的行。这与Impossible WHERE的含义相似,用于SELECT语句。 -
No tables used(JSON 属性:message)查询没有 FROM 子句,或者有一个 FROM DUAL 子句。
对于
INSERT或REPLACE语句,EXPLAIN显示该值,当没有SELECT部分时。例如,它出现在EXPLAIN INSERT INTO t VALUES(10)中,因为它等同于EXPLAIN INSERT INTO t SELECT 10 FROM DUAL。 -
不存在(JSON 属性:message)MySQL 能够对查询进行
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中的每一行,MySQL 只需要在t2中执行单个查找,无论t2中实际有多少行匹配。这也可以表明,一个
WHERE条件的形式为NOT IN (或subquery)NOT EXISTS (已经被内部转换为反连接。这删除了子查询并将其表带入顶层查询计划,从而提高了成本规划。通过合并半连接和反连接,优化器可以更自由地重新排序表,以便在某些情况下生成更快的计划。subquery)您可以通过检查
Message列从SHOW WARNINGS中的执行EXPLAIN,或在EXPLAIN FORMAT=TREE的输出中,来查看给定查询是否执行了反连接转换。Note反连接是半连接
的补充。反连接返回所有来自table_aJOINtable_bONconditiontable_a的行,其中没有在table_b中找到匹配condition的行。 -
计划还没有准备好(JSON 属性:none)这是在使用
EXPLAIN FOR CONNECTION时优化器尚未完成执行计划的创建时出现的值。如果执行计划输出包含多行,则其中任何或所有行都可能具有该Extra值,具体取决于优化器确定完整执行计划的进度。 -
Range checked for each record (index map:(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 是候选项。 -
Recursive(JSON 属性:recursive)这表明该行应用于递归
SELECT部分的公共表表达式。请参阅 第 15.2.20 节,“WITH (公共表表达式)”。 -
Rematerialize(JSON 属性:rematerialize)Rematerialize (X,...)在EXPLAIN行中显示为表T,其中X是任何侧面导出表,其重新物化被触发时读取T的新行。例如:SELECT ... FROM t, LATERAL (derived table that refers to t) AS dt ...派生表的内容被重新materialized,以便在处理每一行
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)这表明了半连接 Duplicate Weedout 策略中的临时表使用。
-
unique row not found(JSON 属性:message)对于查询
SELECT ... FROM,没有行满足表的tbl_nameUNIQUE索引或PRIMARY KEY条件。 -
Using filesort(JSON 属性:using_filesort)MySQL 必须执行额外的传递以确定如何检索行的排序顺序。排序是通过遍历所有行根据连接类型和存储排序键和指向行的指针来实现的。然后,键被排序,行被检索以排序顺序。见 第 10.2.1.16 节,“ORDER BY 优化”。
-
Using index(JSON 属性:using_index)列信息从索引树中检索,而不需要执行额外的查找以读取实际行。这一策略可以在查询仅使用单个索引的列时使用。
对于具有用户定义的聚簇索引的
InnoDB表,该索引可以在Extra列中没有Using index时使用。这是在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)表示使用 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)表示使用批量键访问算法,(hash join)表示使用哈希连接。即,从前一行的表中缓冲键,然后从表中批量读取匹配的行。在 JSON 格式的输出中,
using_join_buffer的值始终是Block Nested Loop、Batched Key Access或hash join之一。有关哈希连接的更多信息,请见 第 10.2.1.4 节,“哈希连接优化”,和 Block Nested-Loop 连接算法。
见 批量键访问连接,以获取关于批量键访问算法的信息。
-
Using MRR(JSON 属性:message)表使用 Multi-Range Read 优化策略读取。见 第 10.2.1.11 节,“Multi-Range Read 优化”。
-
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表 only。这意味着 NDB Cluster 正在使用 Condition Pushdown 优化来提高直接比较非索引列和常量的效率。在这种情况下,条件被 “推送” 到集群的数据节点,并在所有数据节点上同时评估。这消除了将非匹配行发送到网络的需要,可以将查询速度提高 5 到 10 倍,相比于 Condition Pushdown 未被使用的情况。有关更多信息,请参阅 第 10.2.1.5 节,“引擎 Condition Pushdown 优化”。 -
Zero limit(JSON 属性:message)该查询具有
LIMIT 0子句,无法选择任何行。
您可以通过将 EXPLAIN 输出中的 rows 列的值相乘来获得 join 的好坏程度。这应该告诉您大致需要检查多少行来执行查询。如果您使用 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 ttActualPCCHAR(10)ttAssignedPCCHAR(10)ttClientIDCHAR(10)etEMPLOYIDCHAR(15)doCUSTNMBRCHAR(15) -
表具有以下索引。
Table Index ttActualPCttAssignedPCttClientIDetEMPLOYID(primary key)doCUSTNMBR(primary key) -
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
这还不是完美的,但已经好了很多:行数值的乘积减少了 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 连接优化器的有根据的猜测。可以通过比较 rows 产品与查询实际返回的行数来检查数字是否接近真实。如果数字差别很大,可以尝试在 SELECT 语句中使用 STRAIGHT_JOIN 并尝试以不同的顺序列出 FROM 子句中的表。(但是,STRAIGHT_JOIN 可能会阻止索引的使用,因为它禁用了半连接变换。见 使用半连接变换优化 IN 和 EXISTS 子查询谓词。)
在某些情况下,可以在使用 EXPLAIN SELECT 时执行修改数据的语句;有关更多信息,请参见 第 15.2.15.8 节,“派生表”。