Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


10.8.2 EXPLAIN 输出格式

EXPLAIN 语句提供了 MySQL 执行语句的信息。EXPLAIN 可以与SELECTDELETEINSERTREPLACEUPDATE 语句一起使用。

EXPLAIN 返回了 SELECT 语句中每个表的信息。它将输出结果中的表按 MySQL 处理语句时读取它们的顺序列出。这意味着 MySQL 首先读取第一个表,然后在第二个表中找到匹配行,接着第三个表,直到所有表都处理完毕。然后,MySQL 输出选择的列,并从最后一个表回溯,直到找到有更多匹配行的表。然后继续读取下一个表。

Note

MySQL Workbench 提供了可视化的 Explain 能力,提供了EXPLAIN 输出的可视化表示。请参阅使用 Explain 提高查询性能教程

本节描述了EXPLAIN 的输出列。后续部分提供了关于typeExtra 列的其他信息。

每个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 附加信息

Note

JSON 属性如果是 NULL 则不在 JSON 格式的 EXPLAIN 输出中显示。

  • id (JSON 名称:select_id)

    SELECT 标识符。这个是查询中的顺序号码。如果该行引用了其他行的 union 结果,那么该行的 table 列将显示一个值,如 <unionM,N>,表示该行引用了 id 值为 MN 的行。

  • select_type (JSON 名称:none)

    SELECT 类型,可以是以下表格中显示的任何类型。 JSON 格式的 EXPLAINSELECT 类型作为 query_block 的属性,除非是 SIMPLEPRIMARY。JSON 名称(如果适用)也在表格中显示。

    select_type Value JSON 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=JSONEXPLAIN 时,输出没有直接等价于 select_type 的单个属性;query_block 属性对应于给定的 SELECT。大多数 SELECT 子查询类型的属性都可用(例如,materialized_from_subquery 对应于 MATERIALIZED),并在适当时显示;没有 JSON 等价于 SIMPLEPRIMARY 的。

    SELECT 语句的select_type 值显示影响的表类型。例如,select_typeDELETE 对于DELETE 语句。

  • table (JSON名称:table_name)

    输出行所指的表名。这也可以是以下值之一:

  • partitions (JSON名称:partitions)

    查询将匹配的分区。该值为非分区表时为 NULL。请参阅第26.3.5节,“获取关于分区的信息”

  • type(JSON名称:access_type)

    连接类型。有关不同类型的描述,请参阅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 (JSON 名称:key)

    key 列指示 MySQL 实际使用的键(索引)。如果 MySQL 决定使用 possible_keys 中的一个索引来查找行,那么该索引将被列为键值。

    key 可能命名一个不在 possible_keys 值中的索引。这可能发生在没有 possible_keys 索引适合查找行,但查询选择的所有列都是某个其他索引的列的情况下。即使该索引不用于确定要检索的行,但是索引扫描比数据行扫描更高效。

    对于 InnoDB,次级索引可能覆盖选择的列,即使查询也选择主键,因为 InnoDB 将每个次级索引与主键值一起存储。如果 keyNULL,MySQL 找不到使用索引来执行查询的方法。

    要强制 MySQL 使用或忽略 possible_keys 列中的索引,可以在查询中使用 FORCE INDEXUSE INDEXIGNORE 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 (JSON名称:key_length)

    key_len列指示MySQL实际使用的键的长度。key_len值使您可以确定多部分键中MySQL实际使用的部分。如果key列显示NULL,那么key_len列也将显示NULL

    由于键存储格式,可能为NULL的列的键长度比不可能为NULL的列长一个。

  • ref (JSON名称:ref)

    ref列显示了哪些列或常量与索引名在key列中比较以从表中选择行。

    如果值是 func,则使用的值是某个函数的结果。要知道哪个函数,请使用SHOW WARNINGS,在EXPLAIN后面,以获取扩展的EXPLAIN输出。该函数可能是一个算术运算符。

  • rows(JSON名称:rows)

    rows 列指示 MySQL 必须检查的行数以执行查询。

    对于InnoDB 表,这个数字是一个估计值,可能不总是准确的。

  • filtered(JSON名称:filtered)

    filtered 列指示估算的表行数被过滤的百分比。最大值是 100,表示没有对行进行过滤。从 100 值递减的数字表示增加的过滤量。rows 显示检查的行数,rows × filtered 显示与下一个表进行连接的行数。例如,如果 rows 是 1000 和 filtered 是 50.00(50%),则需要与下一个表进行连接的行数是 1000 × 50% = 500。

  • Extra (JSON名称:none)

    这个列包含了 MySQL 解析查询的额外信息。关于不同值的描述,请参见EXPLAIN Extra Information

    没有单个 JSON 属性对应于Extra 列;然而,这些值可以作为 JSON 属性,或者是message 属性的文本。

type 列中的EXPLAIN 输出描述了表的连接方式。在 JSON 格式输出中,这些值是access_type 属性的值。以下列表描述了连接类型,从最好到最差:

  • system

    该表只有一个行(= 系统表)。这是一种特殊情况的const 连接类型。

  • 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;
  • eq_ref

    从前一个表读取一行记录,每个组合的结果都来自前一个表。除了systemconst 类型,这是最好的连接类型。它是在所有索引部分都被使用时,且索引是一个PRIMARY KEYUNIQUE 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

    从这个表中读取所有与索引值匹配的行,每个组合的行来自前一个表。ref 如果连接只使用索引的左前缀或索引不是 PRIMARY KEYUNIQUE 索引(换言之,如果连接不能根据键值选择单个行)。如果用到的键匹配的行很少,这是一个好的连接类型。

    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

    使用 FULLTEXT 索引进行连接。

  • ref_or_null

    这个连接类型像 ref,但是 MySQL 还会对包含 NULL 值的行进行额外搜索。这类连接优化最常用于解决子查询。在以下示例中,MySQL 可以使用 ref_or_null 连接来处理 ref_table

    SELECT * FROM ref_table
      WHERE key_column=expr OR key_column IS NULL;

    第10.2.1.15节,“IS NULL 优化”

  • index_merge

    这个连接类型表示使用了索引合并优化。在这种情况下,输出行中的key列包含了用到的索引列表,key_len 列包含了用到的索引的最长键部分。更多信息,请见第10.2.1.3节,“索引合并优化”

  • unique_subquery

    这个类型替代了eq_ref,用于某些以下形式的IN子查询:

    value IN (SELECT primary_key FROM single_table WHERE some_expr)

    unique_subquery只是一个索引查找函数,完全替代了子查询,以提高效率。

  • index_subquery

    这个连接类型与unique_subquery相似。它替代了IN子查询,但用于非唯一索引的以下形式的子查询:

    value IN (SELECT key_column FROM single_table WHERE some_expr)
  • range

    只检索给定范围内的行,使用索引选择行。输出行中的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);
  • index

    索引连接类型与ALL相同,但索引树被扫描。这有两种情况:

    • 如果索引是查询的覆盖索引,可以用来满足所有表数据,仅扫描索引树。在这种情况下,Extra列显示Using index。索引只读取通常比ALL快,因为索引通常小于表数据。

    • 使用索引来查找数据行,进行全表扫描。Uses index不出现在Extra列中。

    MySQL 可以在查询只使用单个索引的列时使用这个连接类型。

  • ALL

    对每个前一个表的行组合进行全表扫描。这通常在第一个表不标记const时不好,通常在所有其他情况下都很糟。通常,可以通过添加使表基于常量值或前一个表的列值来避免ALL

ExtraEXPLAIN 输出的 Extra 列包含 MySQL 解析查询时的额外信息。以下列表解释了该列可能出现的值。每个项目还指示 JSON 格式输出中哪个属性显示 Extra 值。其中一些项目有特定的属性,其他项目将作为 message 属性显示。

如果你想让你的查询尽量快,可以查看 Extra 列值为 Using filesortUsing temporary,或者在 JSON 格式的 EXPLAIN 输出中查看 using_filesortusing_temporary_table 属性等于 true

  • Backward index scan (JSON: backward_index_scan)

    优化器可以使用 InnoDB 表的降序索引。与 Using index 一起显示。更多信息,请参见第 10.3.13 节,“降序索引”

  • 'table' 的子表@1 (JSON: message 文本)

    这个表格在 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(tbl_name)(JSON 属性: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)

    MySQL 已经读取了所有const(和system)表,并注意 WHERE 子句总是假设为 false。

  • LooseScan(m..n)(JSON 属性:message)

    使用了半连接 LooseScan 策略,mn 是键部件号码。

  • No matching min/max row(JSON 属性:message)

    没有行满足查询条件,例如 SELECT MIN(...) FROM ... WHERE condition

  • no matching row in const table(JSON 属性:message)

    对于具有连接的查询,出现了空表或满足唯一索引条件的行为空表。

  • 无匹配行后分区剪枝(JSON 属性:message)

    对于DELETEUPDATE,优化器在分区剪枝后找不到要删除或更新的行。与SELECT 语句中的“不可能WHERE”类似。

  • 无使用表(JSON 属性:message)

    该查询没有FROM子句,或者有FROM DUAL子句。

    对于INSERTREPLACE 语句,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 (subquery) 的 WHERE 条件已经被内部转换为反连接。这将移除子查询,带入顶层查询的计划,为成本规划提供改进。通过合并半连接和反连接,优化器可以更自由地重新排列执行计划,从而在某些情况下产生更快的计划。

    可以通过检查 SHOW WARNINGS 中的 Message 列或 EXPLAIN FORMAT=TREE 的输出来查看某个查询是否执行了反连接变换。

    Note

    反连接是半连接的补充,形式为 table_a JOIN table_b ON condition。反连接返回 table_a 中的所有行,其中没有在 table_b 中找到匹配 condition 的行。

  • Plan isn't ready yet (JSON 属性:none)

    在使用 EXPLAIN FOR CONNECTION 时,优化器还没有完成对命名连接语句的执行计划创建。如果执行计划输出包含多行,那么其中任何一行或所有行都可能具有这个 Extra 值,取决于优化器在确定完整执行计划时的进度。

  • Range checked for each record (index map: N) (JSON 属性:message)

    MySQL 没有找到合适的索引,但是发现一些索引可能在前一个表中的列值被知道后可以使用。对于每个前一个表的行组合,MySQL 检查是否可以使用 rangeindex_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 行中显示,where X 是任何侧向派生表,如果对 T 的新行被读取时触发重新计算。例如:

    SELECT
      ...
    FROM
      t,
      LATERAL (derived table that refers to t) AS dt
    ...

    派生表的内容每次处理 t 的新行时重新计算,以保持最新。

  • 扫描了N 个数据库 (JSON 属性: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)可以通过读取另一个索引行来获取。即,对于每个列c1c2,都存在一个索引,其中该列是索引的第一个列。在这种情况下,返回一行,通过读取两个确定性行生产。

    这个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_tableOpen_frm_onlyOpen_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 indexExtra 列中缺失,也可以使用该索引。这是当typeindexkeyPRIMARY 时的情况。

    用于EXPLAIN FORMAT=TRADITIONALEXPLAIN FORMAT=JSONEXPLAIN 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 BYDISTINCT查询而不需要实际表的磁盘访问。另外,该索引被用于最有效地方式,以便每个组只读取少量索引条目。详见第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 LoopBatched Key Accesshash 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 BYORDER BY 子句,列名不同。

  • Using where(JSON属性:attached_condition)

    使用ALLindex 连接类型,如果Extra 值不是 Using where,可能存在查询错误。

    Using where 没有直接对应的 JSON 格式输出;attached_condition 属性包含任何ALLindex 连接类型。

  • 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 如果将列声明为同一类型和大小,可以更高效地使用索引。在这个上下文中,VARCHARCHAR 如果声明为同一大小,会被认为是相同的。tt.ActualPC 声明为 CHAR(10)et.EMPLOYID 声明为 CHAR(15),因此存在长度不匹配。

为了解决列长度不一致问题,可以使用ALTER TABLEActualPC 从 10 个字符延长到 15 个字符:

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

现在,tt.ActualPCet.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.EMPLOYIDtt.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

rowsEXPLAIN的输出结果是一个MySQL连接优化器的有道理猜测。通过比较rows乘积与实际查询返回的行数,检查这些数字是否接近真实值。如果这些数字差别很大,你可能会通过在SELECT语句中使用STRAIGHT_JOIN,并尝试在FROM子句中将表的顺序改为不同的顺序。然而,STRAIGHT_JOIN可能会禁止索引的使用,因为它禁用半连接变换。见优化IN和EXISTS子查询谓词与半连接变换

在某些情况下,可以使用EXPLAIN SELECT语句执行修改数据的语句;更多信息,请参见第15.2.15.8节,“派生表”