文档首页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
信息 (Gzip) - 4.0Mb
信息 (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  EXPLAIN 输出格式

10.8.2 EXPLAIN 输出格式

EXPLAIN 语句提供有关 MySQL 如何执行语句的信息。EXPLAIN 适用于 SELECTDELETEINSERTREPLACEUPDATE 语句。

EXPLAINSELECT 语句中使用的每个表返回一行信息。它按照 MySQL 在处理语句时读取表的顺序列出输出中的表。这意味着 MySQL 从第一个表中读取一行,然后在第二个表中找到匹配的行,然后在第三个表中找到匹配的行,依此类推。当所有表都处理完毕后,MySQL 输出选定的列,并回溯表列表,直到找到一个还有更多匹配行的表。从该表中读取下一行,并继续处理下一个表。

注意

MySQL Workbench 具有可视化解释功能,可以直观地显示 EXPLAIN 输出。请参阅 教程:使用 Explain 提高查询性能

EXPLAIN 输出列

本节介绍 EXPLAIN 生成的输出列。后面的章节将提供有关 typeExtra 列的更多信息。

EXPLAIN 的每一行输出都提供有关一个表的信息。每一行包含 表 10.1,“EXPLAIN 输出列” 中汇总的值,并在该表后面进行更详细的描述。列名显示在表格的第一列中;第二列提供在使用 FORMAT=JSON 时输出中显示的等效属性名。

表 10.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 附加信息

注意

JSON 格式的 EXPLAIN 输出中不显示 NULL 的 JSON 属性。

  • id (JSON 名称: select_id)

    SELECT 标识符。这是查询中 SELECT 的序号。如果该行引用其他行的联合结果,则该值可以是 NULL。在这种情况下,table 列显示类似 <unionM,N> 的值,表示该行引用 id 值为 MN 的行的联合。

  • select_type (JSON 名称: 无)

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

    select_type JSON 名称 含义
    SIMPLE 简单的 SELECT(不使用 UNION 或子查询)
    PRIMARY 最外层的 SELECT
    UNION UNION 中的第二个或后面的 SELECT 语句
    DEPENDENT UNION dependent (true) UNION 中的第二个或后面的 SELECT 语句,依赖于外部查询
    UNION RESULT union_result UNION 的结果。
    SUBQUERY 子查询中的第一个 SELECT
    DEPENDENT SUBQUERY dependent (true) 子查询中的第一个 SELECT,依赖于外部查询
    DERIVED 派生表
    DEPENDENT DERIVED dependent (true) 依赖于另一个表的派生表
    MATERIALIZED materialized_from_subquery 物化子查询
    UNCACHEABLE SUBQUERY cacheable (false) 一个子查询,其结果不能被缓存,并且必须为外部查询的每一行重新评估
    UNCACHEABLE UNION cacheable (false) UNION 中的第二个或后面的 select,它属于不可缓存的子查询(请参阅 UNCACHEABLE SUBQUERY

    DEPENDENT 通常表示使用相关子查询。请参阅 第 15.2.15.7 节“相关子查询”

    DEPENDENT SUBQUERY 的评估与 UNCACHEABLE SUBQUERY 的评估不同。对于 DEPENDENT SUBQUERY,子查询只针对其外部上下文中每组不同的变量值重新评估一次。对于 UNCACHEABLE SUBQUERY,子查询将针对外部上下文的每一行重新评估。

    当您在 EXPLAIN 中指定 FORMAT=JSON 时,输出没有直接等同于 select_type 的单个属性;query_block 属性对应于给定的 SELECT。等同于上面显示的大多数 SELECT 子查询类型的属性都可用(例如 MATERIALIZEDmaterialized_from_subquery),并在适当时显示。没有 SIMPLEPRIMARY 的 JSON 等效项。

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

  • table(JSON 名称:table_name

    输出行所引用的表的名称。这也可以是以下值之一

    • <unionM,N>:该行引用 id 值为 MN 的行的并集。

    • <derivedN>:该行引用 id 值为 N 的行的派生表结果。例如,派生表可能是 FROM 子句中的子查询的结果。

    • <subqueryN>:该行引用 id 值为 N 的行的物化子查询的结果。请参阅 第 10.2.2.2 节“使用物化优化子查询”

  • partitions(JSON 名称:partitions

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

  • type(JSON 名称:access_type

    连接类型。有关不同类型的描述,请参阅 EXPLAIN 连接类型

  • possible_keys(JSON 名称:possible_keys

    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 语句”第 9.6 节“MyISAM 表维护和崩溃恢复”

  • key_len(JSON 名称:key_length

    key_len 列指示 MySQL 决定使用的键的长度。key_len 的值使您能够确定 MySQL 实际使用了多部分键的多少部分。如果 key 列显示 NULL,则 key_len 列也显示 NULL

    由于键的存储格式,可以为 NULL 的列的键长度比 NOT NULL 列的键长度大 1。

  • ref(JSON 名称:ref

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

    如果值为 func,则使用的值是某个函数的结果。要查看是哪个函数,请在 EXPLAIN 之后使用 SHOW WARNINGS 来查看扩展的 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 名称:无)

    此列包含有关 MySQL 如何解析查询的附加信息。有关不同值的描述,请参阅 EXPLAIN 额外信息

    没有与 Extra 列对应的单个 JSON 属性;但是,此列中可能出现的值将作为 JSON 属性或 message 属性的文本公开。

EXPLAIN 连接类型

EXPLAIN 输出的 type 列描述表的连接方式。在 JSON 格式的输出中,这些值作为 access_type 属性的值出现。以下列表描述了连接类型,按从最佳类型到最差类型的顺序排列

  • system

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

  • const

    该表最多有一个匹配行,该行在查询开始时读取。因为只有一行,所以此行中的列值可以被优化器的其余部分视为常量。const 表速度非常快,因为它们只读取一次。

    当您将 PRIMARY KEYUNIQUE 索引的所有部分与常量值进行比较时,将使用 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

    对于先前表中的每一行组合,都会从此表中读取所有具有匹配索引值的 ​​行。如果连接仅使用键的最左侧前缀,或者键不是 PRIMARY KEYUNIQUE 索引(换句话说,如果连接无法基于键值选择单个行),则使用 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

    使用 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

    此类型替换以下形式的某些 IN 子查询的 eq_ref

    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

    当使用 =<>>>=<<=IS NULL<=>BETWEENLIKEIN() 运算符将键列与常量进行比较时,可以使用 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

    index 连接类型与 ALL 相同,只是扫描了索引树。这以两种方式发生

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

    • 使用从索引读取以按索引顺序查找数据行来执行全表扫描。Uses index 不会出现在 Extra 列中。

    当查询仅使用属于单个索引的列时,MySQL 可以使用这种连接类型。

  • ALL

    对先前表中的每一行组合执行全表扫描。如果该表是第一个未标记为 const 的表,这通常不好,并且在所有其他情况下通常非常糟糕。通常,您可以通过添加索引来避免 ALL,这些索引允许根据常量值或先前表中的列值从表中检索行。

EXPLAIN 额外信息

EXPLAIN 输出的 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 节“降序索引”

  • Child of 'table' pushed join@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)支持以简单快速的方式删除所有表行的处理程序方法。如果引擎使用此优化,则会显示此 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 子句始终为假,并且无法选择任何行。

  • Impossible WHERE(JSON 属性:message

    WHERE 子句始终为假,并且无法选择任何行。

  • Impossible WHERE noticed after reading const tables(JSON 属性:message

    MySQL 已读取所有 const(和 system)表,并注意到 WHERE 子句始终为假。

  • 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

    对于具有连接的查询,存在一个空表或一个没有行满足唯一索引条件的表。

  • No matching rows after partition pruning(JSON 属性:message

    对于 DELETEUPDATE,优化器在分区修剪后没有找到要删除或更新的内容。它的含义类似于 SELECT 语句的 Impossible WHERE

  • No tables used(JSON 属性:message

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

    对于 INSERTREPLACE 语句,当没有 SELECT 部分时,EXPLAIN 会显示此值。例如,它会出现在 EXPLAIN INSERT INTO t VALUES(10) 中,因为它等同于 EXPLAIN INSERT INTO t SELECT 10 FROM DUAL

  • 不存在(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 并使用 t1.id 的值查找 t2 中的行。如果 MySQL 在 t2 中找到匹配的行,它就知道 t2.id 永远不可能是 NULL,并且不会扫描 t2 中具有相同 id 值的其余行。换句话说,对于 t1 中的每一行,MySQL 只需要在 t2 中进行一次查找,而不管 t2 中实际匹配多少行。

    这也可以表明,形式为 NOT IN (subquery)NOT EXISTS (subquery)WHERE 条件已在内部转换为反连接。这将删除子查询并将其表引入最顶层查询的计划中,从而改进了成本规划。通过合并半连接和反连接,优化器可以更自由地重新排列执行计划中的表,在某些情况下可以生成更快的计划。

    您可以通过检查执行 EXPLAINSHOW WARNINGSMessage 列或 EXPLAIN FORMAT=TREE 的输出,查看何时对给定查询执行反连接转换。

    注意

    反连接是半连接 table_a JOIN table_b ON condition 的补充。反连接返回 table_a 中所有在 table_b没有匹配 condition 的行的行。

  • 计划尚未准备好(JSON 属性:无)

    当优化器尚未完成为在命名连接中执行的语句创建执行计划时,使用 EXPLAIN FOR CONNECTION 会出现此值。如果执行计划输出包含多行,则根据优化器确定完整执行计划的进度,其中任何一行或所有行都可能具有此 Extra 值。

  • 已为每条记录检查范围(索引映射:N(JSON 属性:message

    MySQL 没有找到要使用的良好索引,但发现知道前面表中的列值后,可以使用某些索引。对于前面表中的每个行组合,MySQL 都会检查是否可以使用 rangeindex_merge 访问方法来检索行。这并不是很快,但比完全不使用索引执行连接要快。适用性标准如 第 10.2.1.2 节“范围优化”第 10.2.1.3 节“索引合并优化” 中所述,但前面表的所有列值都是已知的并被视为常量。

    索引从 1 开始编号,顺序与 SHOW INDEX 为表显示的顺序相同。索引映射值 N 是一个位掩码值,指示哪些索引是候选项。例如,值 0x19(二进制 11001)表示索引 1、4 和 5 被考虑。

  • 递归(JSON 属性:recursive

    这表明该行适用于递归公共表表达式的递归 SELECT 部分。请参阅 第 15.2.20 节“WITH(公共表表达式)”

  • 重新实例化(JSON 属性:rematerialize

    在表 TEXPLAIN 行中显示 Rematerialize (X,...),其中 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) 是一个覆盖索引。使用此索引,必须扫描 c1 <= 10 的所有行以找到最小 c2 值。相反,考虑此查询

    SELECT MIN(c2) FROM t1 WHERE c1 = 10;

    在这种情况下,c1 = 10 的第一行索引包含最小 c2 值。只需读取一行即可生成返回的行。

    对于为每个表维护精确行计数的存储引擎(例如 MyISAM,而不是 InnoDB),对于 COUNT(*) 查询,如果缺少 WHERE 子句或 WHERE 子句始终为真并且没有 GROUP BY 子句,则可能会出现此 Extra 值。(这是隐式分组查询的一个实例,其中存储引擎会影响是否可以读取确定数量的行。)

  • Skip_open_tableOpen_frm_onlyOpen_full_table(JSON 属性:message

    这些值指示适用于 INFORMATION_SCHEMA 表的查询的文件打开优化。

    • Skip_open_table:不需要打开表文件。信息已经从数据字典中获得。

    • Open_frm_only:只需要读取数据字典以获取表信息。

    • Open_full_table:未优化的信息查找。必须从数据字典和通过读取表文件来读取表信息。

  • Start temporaryEnd temporary(JSON 属性:message

    这表示半连接重复数据删除策略使用临时表。

  • 未找到唯一行(JSON 属性:message

    对于 SELECT ... FROM tbl_name 等查询,没有行满足表上的 UNIQUE 索引或 PRIMARY KEY 的条件。

  • 正在使用文件排序(JSON 属性:using_filesort

    MySQL 必须执行额外的传递才能找出如何按排序顺序检索行。排序是通过根据连接类型遍历所有行并存储所有匹配 WHERE 子句的行的排序键和指向该行的指针来完成的。然后对键进行排序,并按排序顺序检索行。请参阅 第 10.2.1.16 节“ORDER BY 优化”

  • 正在使用索引(JSON 属性:using_index

    仅使用索引树中的信息从表中检索列信息,而无需执行额外的查找来读取实际的行。当查询仅使用属于单个索引的列时,可以使用此策略。

    对于具有用户定义的聚集索引的 InnoDB 表,即使 Extra 列中缺少 Using index,也可以使用该索引。如果 typeindex 并且 keyPRIMARY,则会出现这种情况。

    有关使用的任何覆盖索引的信息,请参见 EXPLAIN FORMAT=TRADITIONALEXPLAIN FORMAT=JSON。它也显示在 EXPLAIN FORMAT=TREE 中。

  • 正在使用索引条件(JSON 属性:using_index_condition

    通过访问索引元组并首先对其进行测试以确定是否读取完整的表行来读取表。这样,索引信息用于延迟(下推)读取完整的表行,除非有必要。请参阅 第 10.2.1.6 节“索引条件下推优化”

  • 正在使用索引进行分组(JSON 属性:using_index_for_group_by

    Using index 表访问方法类似,Using index for group-by 指示 MySQL 找到了一个索引,可以使用该索引检索 GROUP BYDISTINCT 查询的所有列,而无需对实际表进行任何额外的磁盘访问。此外,以最有效的方式使用索引,以便对于每个组,只读取几个索引条目。有关详细信息,请参阅 第 10.2.1.17 节“GROUP BY 优化”

  • 正在使用索引进行跳跃扫描(JSON 属性:using_index_for_skip_scan

    指示正在使用跳跃扫描访问方法。请参阅 跳跃扫描范围访问方法

  • 正在使用连接缓冲区(块嵌套循环)正在使用连接缓冲区(批处理键访问)正在使用连接缓冲区(哈希连接)(JSON 属性:using_join_buffer

    来自先前连接的表被分批读入连接缓冲区,然后从缓冲区中使用它们的行来执行与当前表的连接。(Block Nested Loop) 表示使用块嵌套循环算法,(Batched Key Access) 表示使用批处理键访问算法,(hash join) 表示使用哈希连接。也就是说,来自 EXPLAIN 输出的上一行的表的键被缓冲,并且匹配的行从出现 Using join buffer 的行所表示的表中分批获取。

    在 JSON 格式的输出中,using_join_buffer 的值始终是 Block Nested LoopBatched Key Accesshash join 之一。

    有关哈希连接的更多信息,请参见 第 10.2.1.4 节“哈希连接优化”

    有关批处理键访问算法的信息,请参见 批处理键访问连接

  • 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

    WHERE 子句用于限制与下一个表匹配或发送到客户端的行。除非您特别打算从表中获取或检查所有行,否则如果 Extra 值不是 Using where 并且表连接类型是 ALLindex,则您的查询中可能有问题。

    Using where 在 JSON 格式的输出中没有直接的对应项;attached_condition 属性包含使用的任何 WHERE 条件。

  • Using where with pushed condition(JSON 属性:message

    此项适用于 NDB 表。这意味着 NDB 集群正在使用条件下推优化来提高非索引列和常量之间直接比较的效率。在这种情况下,条件被“下推”到集群的数据节点,并在所有数据节点上同时进行评估。这消除了通过网络发送不匹配行的需要,并且可以使此类查询的速度比可以使用但未使用条件下推的情况快 5 到 10 倍。有关更多信息,请参见 第 10.2.1.5 节“引擎条件下推优化”

  • Zero limit(JSON 属性:message

    查询有一个 LIMIT 0 子句,并且无法选择任何行。

EXPLAIN 输出解释

您可以通过将 EXPLAIN 输出的 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;

对于本例,请做出以下假设

  • 要比较的列已声明如下。

    数据类型
    tt ActualPC CHAR(10)
    tt AssignedPC CHAR(10)
    tt ClientID CHAR(10)
    et EMPLOYID CHAR(15)
    do CUSTNMBR CHAR(15)
  • 这些表具有以下索引。

    索引
    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 = 45,268,558,720 行。如果表更大,您可以想象它需要多长时间。

这里的一个问题是,如果将列声明为相同类型和大小,则 MySQL 可以更有效地使用列上的索引。在这种情况下,如果 VARCHARCHAR 声明为相同大小,则它们被视为相同。tt.ActualPC 被声明为 CHAR(10),而 et.EMPLOYIDCHAR(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

这并不完美,但要好得多:rows 值的乘积减少了 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

EXPLAIN 输出中的 rows 列是 MySQL 连接优化器的推测结果。通过将 rows 乘积与查询返回的实际行数进行比较,检查这些数字是否接近真相。如果数字差异很大,则您可能会通过在 SELECT 语句中使用 STRAIGHT_JOIN 并尝试以不同的顺序列出 FROM 子句中的表来获得更好的性能。(但是,STRAIGHT_JOIN 可能会阻止使用索引,因为它禁用了半连接转换。请参见 使用半连接转换优化 IN 和 EXISTS 子查询谓词。)

在某些情况下,当 EXPLAIN SELECT 与子查询一起使用时,可以执行修改数据的语句;有关更多信息,请参见 第 15.2.15.8 节“派生表”