该EXPLAIN
语句提供有关 MySQL 如何执行语句的信息。
EXPLAIN
适用于
SELECT
,
DELETE
,
INSERT
,
REPLACE
, 和
UPDATE
语句。
EXPLAIN
SELECT
为语句中使用的每个表返回一行信息
。它按照 MySQL 在处理语句时读取它们的顺序列出输出中的表。MySQL 使用嵌套循环连接方法解析所有连接。这意味着 MySQL 从第一个表中读取一行,然后在第二个表、第三个表等中找到匹配的行。当所有的表都被处理完后,MySQL 将选择的列输出,并在表列表中回溯,直到找到一个有更多匹配行的表。从此表中读取下一行,然后继续处理下一个表。
EXPLAIN
输出包括分区信息。此外,对于SELECT
语句,EXPLAIN
生成可以显示的扩展信息
SHOW WARNINGS
(
EXPLAIN
请参阅
第 8.8.3 节,“扩展 EXPLAIN 输出格式”)。
在较旧的 MySQL 版本中,分区和扩展信息是使用
EXPLAIN
PARTITIONS
和
生成的EXPLAIN
EXTENDED
。这些语法仍然被认为是为了向后兼容,但分区和扩展输出现在默认启用,因此PARTITIONS
和EXTENDED
关键字是多余的并且已弃用。它们的使用会导致警告;EXPLAIN
希望在未来的 MySQL 版本中将
它们从语法中删除。
您不能在同一
语句中同时使用 deprecatedPARTITIONS
和关键字。此外,这两个关键字都不能与
选项一起使用。
EXTENDED
EXPLAIN
FORMAT
MySQL Workbench 具有 Visual Explain 功能,可提供
EXPLAIN
输出的可视化表示。请参阅
教程:使用 Explain 提高查询性能。
本节介绍 生成的输出列
EXPLAIN
。后面的部分提供了有关
type
和
Extra
列的附加信息。
每个输出行都EXPLAIN
提供有关一个表的信息。每行包含
表 8.1“EXPLAIN 输出列”中汇总的值,并在表后进行了更详细的描述。列名显示在表的第一列中;第二列提供了
FORMAT=JSON
使用时输出中显示的等效属性名称。
表 8.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 |
没有任何 | 附加信息 |
NULL
不显示在 JSON 格式EXPLAIN
输出
中的
JSON 属性。
SELECT
标识符 。这是查询中的序号SELECT
。NULL
如果该行引用其他行的并集结果,则该值可以是。在这种情况下,该table
列显示一个值 like 表示该行引用具有值 和 的行的 并 集。<union
M
,N
>id
M
N
的类型
SELECT
,可以是下表中显示的任何一种。JSON 格式将类型EXPLAIN
公开SELECT
为 a 的属性query_block
,除非它是SIMPLE
orPRIMARY
。JSON 名称(如果适用)也显示在表中。select_type
价值JSON 名称 意义 SIMPLE
没有任何 简单 SELECT
(不使用UNION
或子查询)PRIMARY
没有任何 最外层 SELECT
UNION
没有任何 中的第二个或后面 SELECT
的语句UNION
DEPENDENT UNION
dependent
(true
)a 中的第二个或后面 SELECT
的语句UNION
,取决于外部查询UNION RESULT
union_result
的结果 UNION
。SUBQUERY
没有任何 SELECT
子查询中的第一个DEPENDENT SUBQUERY
dependent
(true
)首先 SELECT
在子查询中,依赖于外部查询DERIVED
没有任何 派生表 MATERIALIZED
materialized_from_subquery
物化子查询 UNCACHEABLE SUBQUERY
cacheable
(false
)无法缓存结果且必须为外部查询的每一行重新评估的子查询 UNCACHEABLE UNION
cacheable
(false
)UNION
属于不可缓存子查询 的第二个或后面的选择(参见UNCACHEABLE SUBQUERY
)DEPENDENT
通常表示使用相关子查询。请参阅 第 13.2.10.7 节,“相关子查询”。DEPENDENT SUBQUERY
评价不同于UNCACHEABLE SUBQUERY
评价。对于DEPENDENT SUBQUERY
,子查询仅针对来自其外部上下文的每组不同变量值重新评估一次。对于UNCACHEABLE SUBQUERY
,为外部上下文的每一行重新评估子查询。子查询的可缓存性不同于查询缓存中查询结果的缓存(在 第 8.10.3.1 节,“查询缓存如何操作”中描述)。子查询缓存发生在查询执行期间,而查询缓存仅在查询执行完成后才用于存储结果。
当您指定
FORMAT=JSON
withEXPLAIN
时,输出没有直接等同于 的单个属性select_type
;该query_block
属性对应于给定的SELECT
. 与刚刚显示的大多数子查询类型等效的属性SELECT
可用(例如materialized_from_subquery
forMATERIALIZED
),并在适当的时候显示。SIMPLE
或没有 JSON 等价物PRIMARY
。select_type
非语句 的值SELECT
显示受影响表的语句类型。例如,select_type
isDELETE
forDELETE
语句。输出行所引用的表的名称。这也可以是以下值之一:
<union
: 该行是指具有 和M
,N
>id
值 的行的M
并 集N
。<derived
:该行引用值为 的行的派生表N
>id
结果N
。例如,派生表可能来自FROM
子句中的子查询。<subquery
:该行引用值为 的行的具体化子查询的N
>id
结果N
。请参阅 第 8.2.2.2 节,“使用物化优化子查询”。
partitions
(JSON 名称partitions
:)查询将匹配记录的分区。该值适用
NULL
于非分区表。请参阅 第 22.3.5 节,“获取有关分区的信息”。连接类型。有关不同类型的说明,请参阅
EXPLAIN
联接类型。possible_keys
(JSON 名称possible_keys
:)该
possible_keys
列指示 MySQL 可以从中选择的索引来查找该表中的行。请注意,此列完全独立于 的输出中显示的表的顺序EXPLAIN
。这意味着 中的某些键在possible_keys
实践中可能无法用于生成的表顺序。如果此列是
NULL
(或在 JSON 格式的输出中未定义),则没有相关索引。在这种情况下,您可以通过检查WHERE
子句来检查它是否引用适合索引的某个或多个列来提高查询的性能。如果是这样,请创建一个适当的索引并EXPLAIN
再次检查查询。请参阅 第 13.1.8 节,“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
。请参阅第 8.9.4 节,“索引提示”。对于
MyISAM
表,运行ANALYZE TABLE
有助于优化器选择更好的索引。对于MyISAM
表,myisamchk --analyze做同样的事情。请参阅 第 13.7.2.1 节,“ANALYZE TABLE 语句”和 第 7.6 节,“MyISAM 表维护和崩溃恢复”。该
key_len
列指示 MySQL 决定使用的密钥的长度。的值key_len
使您能够确定 MySQL 实际使用多部分键的多少部分。如果key
专栏说NULL
,key_len
专栏也说NULL
。由于密钥存储格式的原因,列的密钥长度可以
NULL
比列的密钥长度大一个NOT 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
列的单个 JSON 属性;但是,此列中可能出现的值作为 JSON 属性或属性文本公开message
。
输出列描述type
了
EXPLAIN
表是如何连接的。在 JSON 格式的输出中,这些作为access_type
属性的值被发现。下面的列表描述了连接类型,从最好的类型到最差的排序:
该表只有一行(=系统表)。
const
这是连接类型 的一个特例 。该表最多有一个匹配行,在查询开始时读取。因为只有一行,所以优化器的其余部分可以将这一行中列的值视为常量。
const
表非常快,因为它们只读一次。const
PRIMARY KEY
当您将 a或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
andconst
类型,这是最好的连接类型。当连接使用索引的所有部分并且索引是PRIMARY KEY
orUNIQUE 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
orUNIQUE
索引(换句话说,如果连接不能根据键值选择单个行),则使用。如果使用的键只匹配几行,这是一个很好的连接类型。ref
可用于使用=
or<=>
运算符进行比较的索引列。在以下示例中,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
索引执行的。这种连接类型类似于 ,但 MySQL 对包含值
ref
的行进行了额外的搜索。NULL
这种连接类型优化最常用于解析子查询。在以下示例中,MySQL 可以使用ref_or_null
连接来处理ref_table
:SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
此连接类型表示使用索引合并优化。在这种情况下,
key
输出行中的列包含使用的索引列表,并key_len
包含使用的索引的最长键部分列表。有关详细信息,请参阅 第 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
适用于此类型。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
通过添加索引来避免,这些索引允许根据常量值或早期表中的列值从表中检索行。
输出列Extra
包含
EXPLAIN
有关 MySQL 如何解析查询的附加信息。以下列表解释了可以出现在该列中的值。每个项目还为 JSON 格式的输出指示哪个属性显示Extra
值。对于其中一些,有一个特定的属性。其他显示为message
属性的文本。
如果您想尽可能快地进行查询,请注意 和 的Extra
列值Using
filesort
,Using temporary
或者在 JSON 格式的EXPLAIN
输出中,
注意using_filesort
和
using_temporary_table
等于 的属性
true
。
Child of '
(JSON:table
' pushed join@1message
文本)该表被引用为
table
可以向下推送到 NDB 内核的连接中的子表。仅在启用下推连接时适用于 NDB Cluster。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
:)semijoin 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
:)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
forSELECT
语句。No tables used
(JSON 属性message
:)查询没有
FROM
子句,或有FROM DUAL
子句。对于
INSERT
orREPLACE
语句,EXPLAIN
当没有SELECT
部分时显示此值。例如,它出现是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
并查找行 。如果 MySQL 在中找到匹配的行 ,它知道 永远不可能 ,并且不会扫描具有相同值的其余行。换句话说,对于 中的每一行,MySQL 只需要在 中进行一次查找,而不管 中有多少行实际匹配。t2
t1.id
t2
t2.id
NULL
t2
id
t1
t2
t2
Plan isn't ready yet
(JSON 属性:无)EXPLAIN FOR CONNECTION
当优化器尚未完成为在命名连接中执行的语句创建执行计划时, 会出现此值。如果执行计划输出包含多行,则其中任何一行或所有行都可能具有此Extra
值,具体取决于优化程序确定完整执行计划的进度。Range checked for each record (index map:
(JSON 属性N
)message
:)MySQL 没有找到好的索引可以使用,但发现在知道前面表的列值后,可能会使用某些索引。对于前面表中的每个行组合,MySQL 检查是否可以使用
range
或index_merge
访问方法来检索行。这不是很快,但比执行完全没有索引的连接要快。适用性标准如 第 8.2.1.2 节“范围优化”和 第 8.2.1.3 节“索引合并优化”中所述,除了上表的所有列值都是已知的并被视为常量。Indexes are numbered beginning with 1, in the same order as shown by
SHOW INDEX
for the table. The index map valueN
is a bitmask value that indicates which indexes are candidates. For example, a value of0x19
(binary 11001) means that indexes 1, 4, and 5 are considered.Scanned
(JSON property:N
databasesmessage
)This indicates how many directory scans the server performs when processing a query for
INFORMATION_SCHEMA
tables, as described in Section 8.2.3, “Optimizing INFORMATION_SCHEMA Queries”. The value ofN
can be 0, 1, orall
.Select tables optimized away
(JSON property:message
)The optimizer determined 1) that at most one row should be returned, and 2) that to produce this row, a deterministic set of rows must be read. When the rows to be read can be read during the optimization phase (for example, by reading index rows), there is no need to read any tables during query execution.
The first condition is fulfilled when the query is implicitly grouped (contains an aggregate function but no
GROUP BY
clause). The second condition is fulfilled when one row lookup is performed per index used. The number of indexes read determines the number of rows to read.Consider the following implicitly grouped query:
SELECT MIN(c1), MIN(c2) FROM t1;
Suppose that
MIN(c1)
can be retrieved by reading one index row andMIN(c2)
can be retrieved by reading one row from a different index. That is, for each columnc1
andc2
, there exists an index where the column is the first column of the index. In this case, one row is returned, produced by reading two deterministic rows.This
Extra
value does not occur if the rows to read are not deterministic. Consider this query:SELECT MIN(c2) FROM t1 WHERE c1 <= 10;
Suppose that
(c1, c2)
is a covering index. Using this index, all rows withc1 <= 10
must be scanned to find the minimumc2
value. By contrast, consider this query:SELECT MIN(c2) FROM t1 WHERE c1 = 10;
In this case, the first index row with
c1 = 10
contains the minimumc2
value. Only one row must be read to produce the returned row.For storage engines that maintain an exact row count per table (such as
MyISAM
, but notInnoDB
), thisExtra
value can occur forCOUNT(*)
queries for which theWHERE
clause is missing or always true and there is noGROUP BY
clause. (This is an instance of an implicitly grouped query where the storage engine influences whether a deterministic number of rows can be read.)Skip_open_table
,Open_frm_only
,Open_full_table
(JSON property:message
)These values indicate file-opening optimizations that apply to queries for
INFORMATION_SCHEMA
tables, as described in Section 8.2.3, “Optimizing INFORMATION_SCHEMA Queries”.Skip_open_table
: Table files do not need to be opened. The information has already become available within the query by scanning the database directory.Open_frm_only
: Only the table's.frm
file need be opened.Open_full_table
: The unoptimized information lookup. The.frm
,.MYD
, and.MYI
files must be opened.
Start temporary
,End temporary
(JSON property:message
)This indicates temporary table use for the semijoin Duplicate Weedout strategy.
unique row not found
(JSON property:message
)For a query such as
SELECT ... FROM
, no rows satisfy the condition for atbl_name
UNIQUE
index orPRIMARY KEY
on the table.Using filesort
(JSON property:using_filesort
)MySQL must do an extra pass to find out how to retrieve the rows in sorted order. The sort is done by going through all rows according to the join type and storing the sort key and pointer to the row for all rows that match the
WHERE
clause. The keys then are sorted and the rows are retrieved in sorted order. See Section 8.2.1.14, “ORDER BY Optimization”.Using index
(JSON property:using_index
)The column information is retrieved from the table using only information in the index tree without having to do an additional seek to read the actual row. This strategy can be used when the query uses only columns that are part of a single index.
For
InnoDB
tables that have a user-defined clustered index, that index can be used even whenUsing index
is absent from theExtra
column. This is the case iftype
isindex
andkey
isPRIMARY
.Using index condition
(JSON property:using_index_condition
)Tables are read by accessing index tuples and testing them first to determine whether to read full table rows. In this way, index information is used to defer (“push down”) reading full table rows unless it is necessary. See Section 8.2.1.5, “Index Condition Pushdown Optimization”.
Using index for group-by
(JSON property:using_index_for_group_by
)Similar to the
Using index
table access method,Using index for group-by
indicates that MySQL found an index that can be used to retrieve all columns of aGROUP BY
orDISTINCT
query without any extra disk access to the actual table. Additionally, the index is used in the most efficient way so that for each group, only a few index entries are read. For details, see Section 8.2.1.15, “GROUP BY Optimization”.Using join buffer (Block Nested Loop)
,Using join buffer (Batched Key Access)
(JSON property:using_join_buffer
)Tables from earlier joins are read in portions into the join buffer, and then their rows are used from the buffer to perform the join with the current table.
(Block Nested Loop)
indicates use of the Block Nested-Loop algorithm and(Batched Key Access)
indicates use of the Batched Key Access algorithm. That is, the keys from the table on the preceding line of theEXPLAIN
output are buffered, and the matching rows are fetched in batches from the table represented by the line in whichUsing join buffer
appears.In JSON-formatted output, the value of
using_join_buffer
is always either one ofBlock Nested Loop
orBatched Key Access
.For more information about these algorithms, see Block Nested-Loop Join Algorithm, and Batched Key Access Joins.
Using MRR
(JSON property:message
)Tables are read using the Multi-Range Read optimization strategy. See Section 8.2.1.10, “Multi-Range Read Optimization”.
Using sort_union(...)
,Using union(...)
,Using intersect(...)
(JSON property:message
)These indicate the particular algorithm showing how index scans are merged for the
index_merge
join type. See Section 8.2.1.3, “Index Merge Optimization”.Using temporary
(JSON property:using_temporary_table
)To resolve the query, MySQL needs to create a temporary table to hold the result. This typically happens if the query contains
GROUP BY
andORDER BY
clauses that list columns differently.Using where
(JSON property:attached_condition
)A
WHERE
clause is used to restrict which rows to match against the next table or send to the client. Unless you specifically intend to fetch or examine all rows from the table, you may have something wrong in your query if theExtra
value is notUsing where
and the table join type isALL
orindex
.Using where
has no direct counterpart in JSON-formatted output; theattached_condition
property contains anyWHERE
condition used.Using where with pushed condition
(JSON property:message
)This item applies to
NDB
tables only. It means that NDB Cluster is using the Condition Pushdown optimization to improve the efficiency of a direct comparison between a nonindexed column and a constant. In such cases, the condition is “pushed down” to the cluster's data nodes and is evaluated on all data nodes simultaneously. This eliminates the need to send nonmatching rows over the network, and can speed up such queries by a factor of 5 to 10 times over cases where Condition Pushdown could be but is not used. For more information, see Section 8.2.1.4, “Engine Condition Pushdown Optimization”.Zero limit
(JSON property:message
)The query had a
LIMIT 0
clause and cannot select any rows.
You can get a good indication of how good a join is by taking
the product of the values in the rows
column of the EXPLAIN
output.
This should tell you roughly how many rows MySQL must examine
to execute the query. If you restrict queries with the
max_join_size
system
variable, this row product also is used to determine which
multiple-table SELECT
statements to execute and which to abort. See
Section 5.1.1, “Configuring the Server”.
The following example shows how a multiple-table join can be
optimized progressively based on the information provided by
EXPLAIN
.
Suppose that you have the
SELECT
statement shown here and
that you plan to examine it using
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;
For this example, make the following assumptions:
The columns being compared have been declared as follows.
Table Column Data Type tt
ActualPC
CHAR(10)
tt
AssignedPC
CHAR(10)
tt
ClientID
CHAR(10)
et
EMPLOYID
CHAR(15)
do
CUSTNMBR
CHAR(15)
The tables have the following indexes.
Table Index tt
ActualPC
tt
AssignedPC
tt
ClientID
et
EMPLOYID
(primary key)do
CUSTNMBR
(primary key)The
tt.ActualPC
values are not evenly distributed.
Initially, before any optimizations have been performed, the
EXPLAIN
statement produces the
following information:
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)
Because type
is
ALL
for each table, this
output indicates that MySQL is generating a Cartesian product
of all the tables; that is, every combination of rows. This
takes quite a long time, because the product of the number of
rows in each table must be examined. For the case at hand,
this product is 74 × 2135 × 74 × 3872 =
45,268,558,720 rows. If the tables were bigger, you can only
imagine how long it would take.
One problem here is that MySQL can use indexes on columns more
efficiently if they are declared as the same type and size. In
this context, VARCHAR
and
CHAR
are considered the same if
they are declared as the same size.
tt.ActualPC
is declared as
CHAR(10)
and et.EMPLOYID
is CHAR(15)
, so there is a length mismatch.
To fix this disparity between column lengths, use
ALTER TABLE
to lengthen
ActualPC
from 10 characters to 15
characters:
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
Now tt.ActualPC
and
et.EMPLOYID
are both
VARCHAR(15)
. Executing the
EXPLAIN
statement again
produces this result:
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
This is not perfect, but is much better: The product of the
rows
values is less by a factor of 74. This
version executes in a couple of seconds.
A second alteration can be made to eliminate the column length
mismatches for the tt.AssignedPC =
et_1.EMPLOYID
and tt.ClientID =
do.CUSTNMBR
comparisons:
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
MODIFY ClientID VARCHAR(15);
After that modification,
EXPLAIN
produces the output
shown here:
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
At this point, the query is optimized almost as well as
possible. The remaining problem is that, by default, MySQL
assumes that values in the tt.ActualPC
column are evenly distributed, and that is not the case for
the tt
table. Fortunately, it is easy to
tell MySQL to analyze the key distribution:
mysql> ANALYZE TABLE tt;
With the additional index information, the join is perfect and
EXPLAIN
produces this result:
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
The rows
column in the output from
EXPLAIN
is an educated guess
from the MySQL join optimizer. Check whether the numbers are
even close to the truth by comparing the
rows
product with the actual number of rows
that the query returns. If the numbers are quite different,
you might get better performance by using
STRAIGHT_JOIN
in your
SELECT
statement and trying to
list the tables in a different order in the
FROM
clause. (However,
STRAIGHT_JOIN
may prevent indexes from
being used because it disables semijoin transformations. See
Section 8.2.2.1, “Optimizing Subqueries, Derived Tables, and View References with Semijoin
Transformations”.)
EXPLAIN
SELECT
与子查询一起使用
时,在某些情况下可以执行修改数据的语句;有关详细信息,请参阅第 13.2.10.8 节,“派生表”。