Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

10.8.2 EXPLAIN 输出格式

EXPLAIN 语句提供了 MySQL 执行语句的信息。EXPLAIN 适用于 SELECTDELETEINSERTREPLACEUPDATE 语句。

EXPLAIN 返回每个表的信息行,用于在 SELECT 语句中使用的表。它按照 MySQL 读取表的顺序列出表。这样,MySQL 读取第一表的行,然后在第二表中找到匹配的行,然后在第三表中找到匹配的行,以此类推。当所有表都被处理后,MySQL 输出所选列并回溯到表列表,直到找到更多匹配行的表。然后,读取该表的下一行,并继续处理下一个表。

Note

MySQL Workbench 具有 Visual Explain 功能,可以提供 EXPLAIN 输出的可视化表示。请参阅 Tutorial: 使用 Explain 提高查询性能

EXPLAIN 输出列

本节描述了 EXPLAIN 生成的输出列。后续部分提供了关于 typeExtra 列的更多信息。

每个 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 额外信息

Note

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

  • id (JSON 名称:select_id)

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

  • select_type (JSON 名称:无)

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

    select_type Value JSON Name Meaning
    SIMPLE 简单的 SELECT(不使用 UNION 或子查询)
    PRIMARY 最外层的 SELECT
    UNION 第二个或后续的 SELECT 语句在 UNION
    DEPENDENT UNION dependent (true) 第二个或后续的 SELECT 语句在 UNION 中,依赖于外部查询
    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 中的第二个或后续的选择,属于不可缓存的子查询(见 UNCACHEABLE SUBQUERY

    DEPENDENT 通常表示相关子查询的使用。见 第 15.2.15.7 节,“相关子查询”

    DEPENDENT SUBQUERY 评估与 UNCACHEABLE SUBQUERY 评估不同。对于 DEPENDENT SUBQUERY,子查询仅在外部上下文的不同值集下重新计算一次。对于 UNCACHEABLE SUBQUERY,子查询将为外部上下文的每一行重新计算。

    当您指定 FORMAT=JSONEXPLAIN 时,输出没有单个属性直接等同于 select_typequery_block 属性对应于给定的 SELECT。大多数 SELECT 子查询类型的等效属性(例如 materialized_from_subquery 对于 MATERIALIZED)可用,并在适当时显示。没有 JSON 等效于 SIMPLEPRIMARY

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

  • 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 输出中的表顺序无关。

    如果该列为 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 决定使用的键的长度。该值使您可以确定 MySQL 实际使用了多少个多部分键的部分。如果 key 列说 NULL,则 key_len 列也说 NULL

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

  • ref (JSON 名称: ref)

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

    如果该值是 func,则使用的值是某些函数的结果。要查看哪个函数,请使用 SHOW WARNINGS following 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 附加信息

    没有单个 JSON 属性对应 Extra 列;然而,该列中的值将作为 JSON 属性或 message 属性的文本公开。

EXPLAIN 连接类型

type 列描述了表如何连接。在 JSON 格式的输出中,这些是 access_type 属性的值。以下列表描述了连接类型,按照从最好到最坏的顺序:

  • system

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

  • const

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

    const用于比较所有PRIMARY KEYUNIQUE索引的所有部分到常量值。在以下查询中,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

    该连接类型表明 Index Merge 优化被使用。在这种情况下,输出行中的key列包含使用的索引列表,而key_len列包含使用的索引的最长键部分。有关更多信息,请参阅第 10.2.1.3 节,“Index Merge 优化”

  • 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, <=>, 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

    index 连接类型与 ALL 相同,除了扫描索引树。这发生在两个情况下:

    • 如果索引是查询的覆盖索引,可以满足表中的所有数据需求,那么只扫描索引树。在这种情况下,Extra 列说 Using index。索引扫描通常比 ALL 快,因为索引的大小通常小于表数据。

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

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

  • ALL

    对每个表的每个组合行进行全表扫描。这通常不是好的,除非该表是第一个未标记为 const 的表,否则通常非常糟糕。在大多数情况下,可以通过添加启用基于常量值或早期表中的列值的行检索来避免 ALL

EXPLAIN 附加信息

EXPLAIN 输出的 Extra 列包含查询解析的附加信息。以下列表解释了该列中的值。每个项目还指示了 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 节,“Descending Indexes”

  • 子表'table'推送连接@1 (JSON: message 文本)

    该表被引用为 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(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,优化器在分区修剪后找不到要删除或更新的行。这与 Impossible WHERE 的含义相似,用于 SELECT 语句。

  • No tables used (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 优化,并且在找到匹配的行后,不再检查该表中的更多行。下面是一个可以这样优化的查询示例:

    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_a JOIN table_b ON condition 的补充。反连接返回所有来自 table_a 的行,其中没有在 table_b 中找到匹配 condition 的行。

  • 计划还没有准备好 (JSON 属性:none)

    这是在使用 EXPLAIN FOR CONNECTION 时优化器尚未完成执行计划的创建时出现的值。如果执行计划输出包含多行,则其中任何或所有行都可能具有该 Extra 值,具体取决于优化器确定完整执行计划的进度。

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

    MySQL 没有找到好的索引来使用,但发现了一些索引可能在前面的表中的列值已知后使用。对于前面的表中的每个行组合,MySQL 都检查是否可以使用 rangeindex_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时保持最新状态。

  • 扫描了 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),这个 Extra 值可以出现在 COUNT(*) 查询中,其中 WHERE 子句不存在或总是 true,并且没有 GROUP BY 子句。(这是一个隐式分组查询,存储引擎影响是否可以读取确定的行数。)

  • 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)

    这表明了半连接 Duplicate Weedout 策略中的临时表使用。

  • unique row not found (JSON 属性:message)

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

  • Using filesort (JSON 属性:using_filesort)

    MySQL 必须执行额外的传递以确定如何检索行的排序顺序。排序是通过遍历所有行根据连接类型和存储排序键和指向行的指针来实现的。然后,键被排序,行被检索以排序顺序。见 第 10.2.1.16 节,“ORDER BY 优化”

  • Using index (JSON 属性:using_index)

    列信息从索引树中检索,而不需要执行额外的查找以读取实际行。这一策略可以在查询仅使用单个索引的列时使用。

    对于具有用户定义的聚簇索引的 InnoDB 表,该索引可以在 Extra 列中没有 Using index 时使用。这是在 typeindexkeyPRIMARY 时的情况。

    对于 EXPLAIN FORMAT=TRADITIONALEXPLAIN 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 BYDISTINCT 查询的所有列的索引,而无需额外的磁盘访问实际表。另外,索引以最有效的方式使用,以便对于每个组,只读取少量索引条目。详见 第 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 LoopBatched Key Accesshash 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 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)

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

  • Zero limit (JSON 属性:message)

    该查询具有 LIMIT 0 子句,无法选择任何行。

EXPLAIN 输出解释

您可以通过将 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
    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 (primary key)
    do CUSTNMBR (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 可以更高效地使用索引,如果它们被声明为相同的类型和大小。在这个上下文中,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

这还不是完美的,但已经好了很多:行数值的乘积减少了 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 节,“派生表”