8.8.2 EXPLAIN 输出格式
EXPLAIN语句提供有关 MySQL 如何执行语句的信息。 EXPLAIN与SELECT,DELETE,INSERT,REPLACE和UPDATE语句一起使用。
EXPLAIN为SELECT语句中使用的每个 table 返回一行信息。它按照 MySQL 在处理语句时读取它们的 Sequences 列出了输出中的 table。 MySQL 使用嵌套循环连接方法解析所有连接。这意味着 MySQL 从第一个 table 中读取一行,然后在第二个 table,第三个 table 中找到匹配的行,依此类推。处理完所有 table 后,MySQL 通过 table 列 table 输出选定的列和回溯,直到找到一个 table,其中存在更多匹配的行。从该 table 中读取下一行,然后 continue 下一个 table。
EXPLAIN输出包括分区信息。同样,对于SELECT语句,EXPLAIN生成扩展信息,该信息可以在EXPLAIN之后与SHOW WARNINGS显示(请参阅第 8.8.3 节“扩展的 EXPLAIN 输出格式”)。
Note
在较早的 MySQL 版本中,分区和扩展信息是使用EXPLAIN PARTITIONS和EXPLAIN EXTENDED生成的。仍然可以识别这些语法的向后兼容性,但是默认情况下现在启用分区和扩展输出,因此PARTITIONS
和EXTENDED
关键字是多余的并且已弃用。使用它们会导致警告,并且在将来的 MySQL 版本中会将它们从EXPLAIN语法中删除。
您不能在同一EXPLAIN语句中一起使用已弃用的PARTITIONS
和EXTENDED
关键字。此外,这两个关键字都不能与FORMAT
选项一起使用。
Note
MySQL Workbench 具有视觉解释功能,该视觉解释提供了EXPLAIN输出的视觉 table 示。参见教程:使用 Explain 改善查询性能。
EXPLAIN 输出列
本节描述EXPLAIN产生的输出列。后面的部分提供有关type和Extra列的其他信息。
EXPLAIN的每个输出行均提供有关一个 table 的信息。每行包含以table8.1,“ EXPLAIN 输出列”汇总的值,并在 table 后更详细地描述。列名显示在 table 的第一列中;第二列提供使用FORMAT=JSON
时在输出中显示的等效属性名称。
table8.1 EXPLAIN 输出列
Column | JSON Name | Meaning |
---|---|---|
id | select_id | SELECT 标识符 |
select_type | None | SELECT 类型 |
table | table_name | 输出行 table |
partitions | partitions | 匹配的分区 |
type | access_type | 联接类型 |
possible_keys | possible_keys | 可能的索引选择 |
key | key | 实际选择的索引 |
key_len | key_length | 所选键的长度 |
ref | ref | 与索引比较的列 |
rows | rows | 估计要检查的行 |
filtered | filtered | 按 table 条件过滤的行百分比 |
Extra | None | Additional information |
Note
NULL
的 JSON 属性不会显示在 JSON 格式的EXPLAIN
输出中。
SELECT标识符。这是查询中SELECT的序号。如果该行引用其他行的并集结果,则值可以为NULL
。在这种情况下,table
列显示类似于<unionM,N>
的值,以指示该行引用具有id
值* M
和 N
*的行的并集。
SELECT的类型,可以是下 table 中显示的任何类型。 JSON 格式的EXPLAIN
会将SELECT
类型公开为query_block
的属性,除非它是SIMPLE
或PRIMARY
。table 格中还显示了 JSON 名称(如果适用)。
select_type 值 | JSON Name | Meaning |
---|---|---|
SIMPLE | None | 简单的SELECT(不使用UNION或子查询) |
PRIMARY | None | Outermost 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 | Derived table |
MATERIALIZED | materialized_from_subquery | Materialized subquery |
UNCACHEABLE SUBQUERY | cacheable ( false ) | 子查询,其结果无法缓存,必须针对外部查询的每一行重新进行评估 |
UNCACHEABLE UNION | cacheable ( false ) | 属于不可缓存子查询的UNION中的第二个或更高版本的选择(请参见UNCACHEABLE SUBQUERY ) |
DEPENDENT
通常 table 示使用相关子查询。参见第 13.2.10.7 节“相关子查询”。
DEPENDENT SUBQUERY
评估不同于UNCACHEABLE SUBQUERY
评估。对于DEPENDENT SUBQUERY
,子查询仅针对其外部上下文中变量的每组不同值重新评估一次。对于UNCACHEABLE SUBQUERY
,将为外部上下文的每一行重新评估子查询。
子查询的可缓存性与查询结果在查询缓存中的缓存(在第 8.10.3.1 节,“查询缓存的操作方式”中描述)不同。子查询缓存在查询执行期间发生,而查询缓存仅在查询执行完成后才用于存储结果。
当您将EXPLAIN
指定为FORMAT=JSON
时,输出将没有与select_type
直接等效的单个属性; query_block
属性对应于给定的SELECT
。可以使用与刚刚显示的大多数SELECT
子查询类型等效的属性(示例是MATERIALIZED
的materialized_from_subquery
),并在适当时显示。 SIMPLE
或PRIMARY
没有 JSON 等效项。
非SELECT语句的select_type
值显示受影响 table 的语句类型。例如,对于DELETE条语句,select_type
是DELETE
。
输出行所引用的 table 的名称。这也可以是以下值之一:
-
<unionM,N>
:行是指具有和N
的id
值的行的并集。-
<derivedN>
:该行引用具有id
值*N
*的行的派生 table 结果。派生 table 可能来自例如FROM
子句中的子查询。 -
<subqueryN>
:该行引用该行的物化子查询的结果,该子查询的id
值为*N
*。参见第 8.2.2.2 节“通过实现来优化子查询”。
-
查询将从中匹配记录的分区。对于非分区 table,该值为NULL
。参见第 22.3.5 节“获取有关分区的信息”。
联接类型。有关不同类型的说明,请参见说明联接类型。
possible_keys
列 table 示 MySQL 可以选择从中查找 table 中各行的索引。请注意,此列完全独立于EXPLAIN的输出中显示的 tableSequences。这意味着possible_keys
中的某些键实际上可能无法用于生成的 tableSequences。
如果此列为NULL
(或在 JSON 格式的输出中未定义),则没有相关的索引。在这种情况下,您可以通过检查WHERE
子句以检查其是否引用某些适合索引的列来提高查询性能。如果是这样,请创建一个适当的索引,然后再次使用EXPLAIN检查查询。参见第 13.1.8 节“ ALTER TABLE 语句”。
要查看 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
。参见第 8.9.4 节“索引提示”。
对于MyISAM
table,运行ANALYZE TABLE可帮助优化器选择更好的索引。对于MyISAM
个 table,myisamchk --analyze相同。参见第 13.7.2.1 节“ ANALYZE TABLE 语句”和第 7.6 节“ MyISAMtable 维护和崩溃恢复”。
key_len
列 table 示 MySQL 决定使用的密钥的长度。值key_len
使您能够确定 MySQL 实际使用的 Multipart 键的多少部分。如果key
列显示NULL
,则key_len
列也显示NULL
。
由于密钥存储格式的原因,可以为NULL
的列的密钥长度比NOT NULL
列的密钥长度大一。
ref
列显示将哪些列或常量与key
列中命名的索引进行比较,以从 table 中选择行。
如果值为func
,则使用的值是某个函数的结果。要查看哪个功能,请在EXPLAIN之后使用SHOW WARNINGS查看扩展的EXPLAIN输出。该函数实际上可能是算术运算符之类的运算符。
rows
列 table 示 MySQL 认为执行查询必须检查的行数。
对于InnoDB个 table,此数字是估计值,可能并不总是准确的。
filtered
列 table 示将被 table 条件过滤的 table 行的估计百分比。最大值为 100,table 示没有行过滤发生。值从 100 减小 table 示过滤量增加。 rows
显示了检查的估计行数,rows
×filtered
显示了将与下 table 连接的行数。例如,如果rows
为 1000 而filtered
为 50.00(50%),则与下 table 连接的行数为 1000×50%= 500.
本列包含有关 MySQL 如何解析查询的其他信息。有关不同值的说明,请参见说明额外信息。
Extra
列没有对应的 JSON 属性;但是,此列中可能出现的值显示为 JSON 属性或message
属性的文本。
说明联接类型
EXPLAIN输出的type
列描述如何联接 table。在 JSON 格式的输出中,这些是access_type
属性的值。以下列 table 描述了连接类型,从最佳类型到最差类型:
该 table 只有一行(=系统 table)。这是const连接类型的特例。
该 table 最多具有一个匹配行,该行在查询开始时读取。因为只有一行,所以优化器的其余部分可以将这一行中列的值视为常量。 consttable 非常快,因为它们只能读取一次。
当您将PRIMARY KEY
或UNIQUE
索引的所有部分与常量值进行比较时,将使用const。在以下查询中,* tbl_name
*可用作consttable:
SELECT * FROM tbl_name WHERE primary_key=1;
SELECT * FROM tbl_name
WHERE primary_key_part1=1 AND primary_key_part2=2;
对于先前 table 中的每行组合,从此 table 中读取一行。除了system和const类型,这是最好的联接类型。当连接使用索引的所有部分且索引为PRIMARY KEY
或UNIQUE NOT NULL
索引时使用。
eq_ref可用于使用=
运算符进行比较的索引列。比较值可以是常量,也可以是使用在此 table 之前读取的 table 中列的 table 达式。在以下示例中,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;
对于先前 table 中的行的每种组合,将从该 table 中读取具有匹配索引值的所有行。如果联接仅使用键的最左前缀,或者键不是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;
See 第 8.2.1.13 节,“ IS NULL 优化”.
此联接类型指示使用索引合并优化。在这种情况下,输出行中的key
列包含使用的索引列 table,而key_len
包含使用的索引的最长键部分的列 table。有关更多信息,请参见第 8.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
。
使用=,<>,>,>=,<,<=,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相同,只是扫描了索引树。这发生两种方式:
-
如果索引是查询的覆盖索引,并且可用于满足 table 中所需的所有数据,则仅扫描索引树。在这种情况下,
Extra
列 table 示Using index
。仅索引扫描通常比ALL快,因为索引的大小通常小于 table 数据。- 使用对索引的读取执行全 table 扫描,以按索引 Sequences 查找数据行。
Uses index
没有出现在Extra
列中。
- 使用对索引的读取执行全 table 扫描,以按索引 Sequences 查找数据行。
当查询仅使用属于单个索引一部分的列时,MySQL 可以使用此联接类型。
对来自先前 table 的行的每个组合进行全 table 扫描。如果该 table 是第一个未标记const的 table,则通常不好,并且在所有其他情况下通常“非常”糟糕。通常,您可以通过添加索引来避免ALL,这些索引允许基于早期 table 中的常量值或列值从 table 中检索行。
EXPLAIN 额外信息
EXPLAIN输出的Extra
列包含有关 MySQL 如何解析查询的其他信息。以下列 table 说明了可以在此列中显示的值。每一项还针对 JSON 格式的输出指示哪个属性显示Extra
值。对于其中一些,有一个特定的属性。其他显示为message
属性的文本。
如果要使查询尽可能快,请注意Using filesort
和Using temporary
的Extra
列值,或者以 JSON 格式的EXPLAIN
输出查找等于true
的using_filesort
和using_temporary_table
属性。
Child of 'table' pushed join@1
(JSON:message
个文本)
该 table 在可以下推到 NDB 内核的 Connecting 被称为* table
*的子级。启用下推联接时,仅适用于 NDB 群集。有关更多信息和示例,请参见ndb_join_pushdown服务器系统变量的描述。
const row not found
(JSON 属性:const_row_not_found
)
对于诸如SELECT ... FROM tbl_name
的查询,该 table 为空。
Deleting all rows
(JSON 属性:message
)
对于DELETE,某些存储引擎(例如MyISAM)支持处理程序方法,该方法以一种简单而快速的方式删除所有 table 行。如果引擎使用此优化,则会显示此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
子句始终为 false,无法选择任何行。
Impossible WHERE
(JSON 属性:message
)
WHERE
子句始终为 false,无法选择任何行。
Impossible WHERE noticed after reading const tables
(JSON 属性:message
)
MySQL 已读取所有const(和system)table,并注意WHERE
子句始终为 false。
LooseScan(m..n)
(JSON 属性:message
)
使用半连接的 LooseScan 策略。 * m
和 n
*是关键 Component 号。
No matching min/max row
(JSON 属性:message
)
没有行满足诸如SELECT MIN(...) FROM ... WHERE condition
之类的查询的条件。
no matching row in const table
(JSON 属性:message
)
对于具有联接的查询,存在一个空 table 或没有满足唯一索引条件的行的 table。
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
条件匹配的一行后,不检查该 table 中的更多行以进行前一行的组合。这是可以通过这种方式优化的查询类型的示例:
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
中实际匹配多少行。
Plan isn't ready yet
(JSON 属性:无)
当优化程序尚未完成为在命名 Connecting 执行的语句的执行计划的创建时,此值出现在连接说明处。如果执行计划输出包含多行,则它们中的任何一个或全部都可以具有Extra
值,这取决于优化程序确定完整执行计划的进度。
Range checked for each record (index map: N)
(JSON 属性:message
)
MySQL 找不到很好的索引来使用,但是发现在知道先前 table 中的列值之后可能会使用某些索引。对于上 table 中的每个行组合,MySQL 检查是否可以使用range或index_merge访问方法来检索行。这不是很快,但是比根本没有索引的连接要快。适用标准如第 8.2.1.2 节“范围优化”和第 8.2.1.3 节“索引合并优化”中所述,不同之处在于上 table 的所有列值都是已知的并且被视为常量。
索引从 1 开始编号,其 Sequences 与 tableSHOW INDEX所示的 Sequences 相同。索引 Map 值* N
*是一个位掩码值,指示哪些索引是候选索引。例如,值0x19
(二进制 11001)table 示将考虑索引 1、4 和 5.
Scanned N databases
(JSON 属性:message
)
这 table 示在处理INFORMATION_SCHEMA
table 的查询时服务器执行的目录扫描次数,如第 8.2.3 节“优化 INFORMATION_SCHEMA 查询”中所述。 * N
*的值可以为 0、1 或all
。
Select tables optimized away
(JSON 属性:message
)
优化器确定 1)最多应返回一行,以及 2)要生成该行,必须读取确定的一组行。当在优化阶段可以读取要读取的行时(例如,通过读取索引行),则在查询执行期间无需读取任何 table。
当查询隐式分组(包含聚合函数但不包含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
值。只读取一行就可以产生返回的行。
对于维护每个 table 的行数精确的存储引擎(例如MyISAM
,但不保留InnoDB
),对于缺少WHERE
子句或始终为 true 且没有GROUP BY
子句的COUNT(*)
查询,可能会出现Extra
值。 (这是一个隐式分组查询的实例,其中存储引擎影响是否可以读取确定数目的行.)
Skip_open_table
,Open_frm_only
,Open_full_table
(JSON 属性:message
)
这些值 table 示适用于INFORMATION_SCHEMA
table 查询的文件打开优化,如第 8.2.3 节“优化 INFORMATION_SCHEMA 查询”中所述。
-
Skip_open_table
:不需要打开 table 文件。通过扫描数据库目录,该信息已在查询中可用。-
Open_frm_only
:仅需要打开 table 的.frm
文件。 -
Open_full_table
:未优化的信息查找。必须打开.frm
,.MYD
和.MYI
文件。
-
-
Start temporary
,End temporary
(JSON 属性:message
)
这 table 明临时 table 用于半联接重复淘汰策略。
unique row not found
(JSON 属性:message
)
对于诸如SELECT ... FROM tbl_name
的查询,没有任何行满足 table 上UNIQUE
索引或PRIMARY KEY
的条件。
Using filesort
(JSON 属性:using_filesort
)
MySQL 必须额外进行一遍,以找出如何按排序 Sequences 检索行。排序是通过根据联接类型遍历所有行并存储与WHERE
子句匹配的所有行的排序键和指向该行的指针来完成的。然后对键进行排序,并按排序 Sequences 检索行。参见第 8.2.1.14 节“按优化排序”。
Using index
(JSON 属性:using_index
)
仅使用索引树中的信息从 table 中检索列信息,而不必进行其他查找以读取实际行。当查询仅使用属于单个索引的列时,可以使用此策略。
对于具有用户定义的聚集索引的InnoDB
table,即使Extra
列中不存在Using index
,也可以使用该索引。如果type
是index且key
是PRIMARY
就是这种情况。
Using index condition
(JSON 属性:using_index_condition
)
通过访问索引 Tuples 并首先对其进行测试以确定是否读取完整的 table 行来读取 table。以此方式,除非必要,否则索引信息用于推迟(“下推”)读取整个 table 行。参见第 8.2.1.5 节“索引条件下推优化”。
Using index for group-by
(JSON 属性:using_index_for_group_by
)
与Using index
table 访问方法类似,Using index for group-by
table 示 MySQL 找到了一个索引,该索引可用于检索GROUP BY
或DISTINCT
查询的所有列,而无需对实际 table 进行任何额外的磁盘访问。此外,以最有效的方式使用索引,因此对于每个组,仅读取少数索引条目。有关详细信息,请参见第 8.2.1.15 节,“通过优化组”。
Using join buffer (Block Nested Loop)
,Using join buffer (Batched Key Access)
(JSON 属性:using_join_buffer
)
来自较早联接的 table 被部分读取到联接缓冲区中,然后从缓冲区中使用它们的行来执行与当前 table 的联接。 (Block Nested Loop)
table 示使用块嵌套循环算法,(Batched Key Access)
table 示使用批处理密钥访问算法。也就是说,对EXPLAIN输出的前一行中的 table 中的键进行缓冲,并从出现Using join buffer
的行所代 table 的 table 中批量提取匹配的行。
在 JSON 格式的输出中,using_join_buffer
的值始终是Block Nested Loop
或Batched Key Access
之一。
有关这些算法的更多信息,请参见块嵌套循环连接算法和批量密钥访问联接。
Using MRR
(JSON 属性:message
)
使用多范围读取优化策略读取 table。参见第 8.2.1.10 节,“多范围读取优化”。
Using sort_union(...)
,Using union(...)
,Using intersect(...)
(JSON 属性:message
)
这些指示了特定算法,该算法显示了如何针对index_merge连接类型合并索引扫描。参见第 8.2.1.3 节“索引合并优化”。
Using temporary
(JSON 属性:using_temporary_table
)
为了解决该查询,MySQL 需要创建一个临时 table 来保存结果。如果查询包含GROUP BY
和ORDER BY
子句以不同的方式列出列,通常会发生这种情况。
Using where
(JSON 属性:attached_condition
)
WHERE
子句用于限制要与下一张 table 匹配或发送到 Client 端的行。除非您专门打算从 table 中获取或检查所有行,否则如果Extra
的值不是Using where
且 table 连接类型为ALL或index,则查询中可能会出错。
Using where
在 JSON 格式的输出中没有直接对应的内容; attached_condition
属性包含使用的任何WHERE
条件。
Using where with pushed condition
(JSON 属性:message
)
此项仅适用于NDB个 table 格。这意味着 NDB Cluster 正在使用条件下推优化来提高在非索引列和常量之间进行直接比较的效率。在这种情况下,条件被“压入”群集的数据节点,并同时在所有数据节点上进行评估。这样就无需通过网络发送不匹配的行,并且在可以但不使用条件下推的情况下,可以将此类查询的速度提高 5 到 10 倍。有关更多信息,请参见第 8.2.1.4 节“引擎状态下推式优化”。
Zero limit
(JSON 属性:message
)
该查询具有LIMIT 0
子句,无法选择任何行。
EXPLAIN 输出解释
通过获取EXPLAIN输出的rows
列中值的乘积,可以很好地 table 明联接的良好程度。这应该大致告诉您 MySQL 必须检查多少行才能执行查询。如果使用max_join_size系统变量限制查询,则此行乘积还用于确定要执行和终止哪些多 tableSELECT语句。参见第 5.1.1 节“配置服务器”。
下面的示例显示如何基于EXPLAIN提供的信息逐步优化多 table 联接。
假设您在此处显示了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 具有以下索引。
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)
因为每个 table 的type
是ALL,所以此输出 table 明 MySQL 正在为所有 table 生成笛卡尔乘积。也就是说,每行的组合。这需要相当长的时间,因为必须检查每个 table 中的行数的乘积。对于手头的情况,此乘积为 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
table 不是这种情况。幸运的是,很容易告诉 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
子句中以不同 Sequences 列出 table 来获得更好的性能。 (但是STRAIGHT_JOIN
可能会禁止使用索引,因为它禁用了半联接转换.请参见第 8.2.2.1 节“使用半联接转换优化子查询,派生 table 和视图引用”。)
在某些情况下,当EXPLAIN SELECT与子查询一起使用时,可能会执行修改数据的语句。有关更多信息,请参见第 13.2.10.8 节“派生 table”。