Skip to main content

2.EXPLAIN 输出格式

EXPLAIN 语句提供有关 MySQL 如何执行语句的信息。 EXPLAIN 与 SELECT、DELETE、INSERT、REPLACE 和 UPDATE 语句配合使用。

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

提示:

MySQL Workbench 具有可视化解释功能,可提供 EXPLAIN 输出的可视化表示。

EXPLAIN 输出列 信息

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

表 10.1 EXPLAIN 输出列

ColumnJSON NameMeaning
idselect_id标识符
select_typeNone类型
tabletable_name输出行的表
partitionspartitions匹配的分区
typeaccess_type连接类型
possible_keyspossible_keys可能选择的索引
keykey实际选择的索引
key_lenkey_length所选键的长度
refref与索引比较的列
rowsrows要检查的行的估计
filteredfiltered按表条件过滤的行的百分比
ExtraNone附加信息

注意:

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

  • id(JSON 名称:select_id)
    • 选择标识符。 这是查询中 SELECT 的序列号。 如果该行引用其他行的并集结果,则该值可以为 NULL。 在这种情况下,表列显示类似 <unionM,N> 的值,以指示该行引用 id 值为 M 和 N 的行的并集。
  • select_type(JSON 名称:无)
    • SELECT 的类型,可以是下表中显示的任何类型。 JSON 格式的 EXPLAIN 将 SELECT 类型公开为 query_block 的属性,除非它是 SIMPLE 或 PRIMARY。 表中还显示了 JSON 名称(如果适用)。
select_type ValueJSON NameMeaning
SIMPLENone简单 SELECT(不使用 UNION 或子查询)
PRIMARYNone最外层 SELECT
UNIONNoneUNION 中的第二个或后续 SELECT 语句
DEPENDENT UNIONdependent (true)UNION 中的第二个或后面的 SELECT 语句,依赖于外部查询
UNION RESULTunion_resultUNION 的结果。
SUBQUERYNone子查询中的第一个 SELECT
DEPENDENT SUBQUERYdependent (true)子查询中的第一个 SELECT,依赖于外部查询
DERIVEDNone派生表
DEPENDENT DERIVEDdependent (true)派生表依赖于另一个表
MATERIALIZEDmaterialized_from_subquery物化子查询
UNCACHEABLE SUBQUERYcacheable (false)无法缓存结果且必须针对外部查询的每一行重新评估结果的子查询
UNCACHEABLE UNIONcacheable (false)UNION 中属于不可缓存子查询的第二个或后续选择

DEPENDENT 通常表示使用相关子查询。

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

当使用 EXPLAIN 指定 FORMAT=JSON 时,输出没有直接等效于 select_type 的单个属性; query_block 属性对应于给定的 SELECT。 与刚刚显示的大多数 SELECT 子查询类型等效的属性均可用(示例为 MATERIALIZED 的 Materialized_from_subquery),并在适当时显示。 SIMPLE 或 PRIMARY 没有等效的 JSON。

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

  • table (JSON name: table_name)

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

    • <union *M* , *N* >:该行指的是id值为M和N的行的并集。
    • <derived *N* >:该行引用id 值为N 的行的派生表结果。例如,派生表可能来自FROM 子句中的子查询。
    • <subquery *N* >:该行引用 id 值为 N 的行的具体化子查询的结果。
  • partitions (JSON name: partitions)

    • 查询将匹配记录的分区。 对于非分区表,该值为 NULL。
  • type (JSON name: access_type)

    • 连接类型。 有关不同类型的说明,请参阅解释联接类型。
  • possible_keys (JSON name: possible_keys)

    • possible_keys 列指示 MySQL 可以选择从中查找该表中的行的索引。 请注意,此列完全独立于 EXPLAIN 输出中显示的表顺序。 这意味着 possible_keys 中的某些键在实践中可能无法按照生成的表顺序使用。
    • 如果此列为 NULL(或在 JSON 格式输出中未定义),则没有相关索引。 在这种情况下,可以通过检查 WHERE 子句来检查它是否引用某些适合建立索引的列,从而提高查询的性能。 如果是这样,请创建适当的索引并再次使用 EXPLAIN 检查查询。
    • 要查看表有哪些索引,请使用 SHOW INDEX FROM tbl_name。
  • key (JSON 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。
    • 对于MyISAM表,运行ANALYZE TABLE可以帮助优化器选择更好的索引。 对于 MyISAM 表,myisamchk --analyze 执行相同的操作。
  • key_len (JSON name: key_length)

    • key_len 列指示 MySQL 决定使用的 key的长度。 key_len 的值使您能够确定 MySQL 实际使用多部分key的多少部分。 如果 key 列显示 NULL,则 key_len 列也显示 NULL。
    • 由于键存储格式的原因,可以为 NULL 的列的键长度比 NOT NULL 列的键长度大 1。
  • ref (JSON name: ref)

    • ref 列显示哪些列或常量与键列中指定的索引进行比较,以从表中选择行。
    • 如果该值为 func,则使用的值是某个函数的结果。 要查看哪个函数,请使用 EXPLAIN 后面的 SHOW WARNINGS 来查看扩展的 EXPLAIN 输出。 该函数实际上可能是一个运算符,例如算术运算符。
  • rows (JSON name: rows)

    • rows 列指示 MySQL 认为它必须检查才能执行查询的行数。
    • 对于 InnoDB 表,此数字是估计值,可能并不总是准确的。
  • filtered (JSON name: filtered)

    • 过滤列指示按表条件过滤的表行的估计百分比。 最大值为 100,这意味着没有发生行过滤。 从 100 开始递减的值表示过滤量的增加。 rows 显示估计的检查行数,rows ×filtered 显示与下表连接的行数。 例如,如果行数为 1000,筛选为 50.00 (50%),则与下表连接的行数为 1000 × 50% = 500。
  • Extra (JSON name: none)

    • 此列包含有关 MySQL 如何解析查询的附加信息。 有关不同值的说明,请参阅 EXPLAIN 额外信息。
    • 没有与 Extra 列对应的单个 JSON 属性; 但是,此列中可能出现的值将作为 JSON 属性或消息属性的文本公开。

EXPLAIN Join 类型

EXPLAIN 输出的类型列描述了表的连接方式。 在 JSON 格式的输出中,这些内容作为 access_type 属性的值找到。 下面的列表描述了连接类型,从最好的类型到最差的类型排序:

  • system

    • 该表只有一行(=系统表)。 这是 const 连接类型的特例。
  • const

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

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

    • 对于前一个表中的行的每个组合,都会从此表中读取一行。 除了 system 和 const 类型之外,这是最好的连接类型。 当连接使用索引的所有部分并且索引是 PRIMARY KEY 或 UNIQUE 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 或 UNIQUE 索引(换句话说,如果联接无法根据键值选择单个行),则使用 ref。 如果使用的键仅匹配几行,那么这是一个很好的连接类型。

    • ref 可用于使用 =<=> 运算符进行比较的索引列。 在以下示例中,MySQL 可以使用 ref join 来处理 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;
  • index_merge

    • 该连接类型表明使用了索引合并优化。 在这种情况下,输出行中的键列包含所使用的索引的列表,并且 key_len 包含所使用的索引的最长键部分的列表。
  • unique_subquery

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

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

    • 使用索引来选择行,仅检索给定范围内的行。 输出行中的键列指示使用哪个索引。 key_len 包含所使用的最长的key部分。 对于该类型,ref 列为 NULL。

    • 当使用 =<>>>=<<=IS NULL<=>BETWEENLIKEIN() 运算符中的任何一个将键列与常量进行比较时,可以使用范围:

      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 列显示“使用索引”。 仅索引扫描通常比 ALL 更快,因为索引的大小通常小于表数据。
      • 全表扫描是通过读取索引来按索引顺序查找数据行来执行的。 使用索引不会出现在 Extra 列中。
    • 当查询仅使用属于单个索引的列时,MySQL 可以使用此联接类型。
  • ALL

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

EXPLAIN Extra 信息

EXPLAIN 输出的 Extra 列包含有关 MySQL 如何解析查询的附加信息。 以下列表解释了此列中可能出现的值。 每个项目还针对 JSON 格式的输出指示哪个属性显示 Extra 值。 其中一些具有特定的属性。 其他显示为消息属性的文本。

如果希望尽可能快地进行查询,请注意使用文件排序和使用临时的额外列值,或者在 JSON 格式的 EXPLAIN 输出中,查找 using_filesort 和 using_temporary_table 属性是否等于 true。

  • Backward index scan (JSON: backward_index_scan)

    • 优化器能够在 InnoDB 表上使用降序索引。 与使用索引一起显示。
  • Child of table pushed join@1 (JSON: message text)

    • 该表被引用为连接中表的子表,可以将其下推到 NDB 内核。 仅在启用下推联接时适用于 NDB Cluster。
  • const row not found (JSON property: const_row_not_found)

    • 对于诸如 SELECT ... FROM tbl_name 之类的查询,该表为空。
  • Deleting all rows (JSON property: message)

    • 对于DELETE,某些存储引擎(例如MyISAM)支持处理程序方法,该方法可以简单快速地删除所有表行。 如果引擎使用此优化,则会显示此额外值。
  • Distinct (JSON property: distinct)

    • MySQL 正在寻找不同的值,因此在找到第一个匹配行后,它会停止为当前行组合搜索更多行。
  • FirstMatch(tbl_name) (JSON property: first_match)

    • 半连接 FirstMatch 连接快捷策略用于 tbl_name。
  • Full scan on NULL key (JSON property: message)

    • 当优化器无法使用索引查找访问方法时,作为后备策略的子查询优化会发生这种情况。
  • Impossible HAVING (JSON property: message)

    • HAVING 子句始终为 false,并且无法选择任何行。
  • Impossible WHERE (JSON property: message)

    • WHERE 子句始终为 false,并且无法选择任何行。
  • Impossible WHERE noticed after reading const tables (JSON property: message)

    • MySQL 已读取所有 const(和系统)表,并注意到 WHERE 子句始终为 false。
  • LooseScan(m..n) (JSON property: message)

    • 使用半连接LooseScan策略。 m 和 n 是关键零件号。
  • No matching min/max row (JSON property: message)

    • 没有行满足查询条件,例如 SELECT MIN(...) FROM ... WHERE 条件。
  • no matching row in const table (JSON property: message)

    • 对于使用联接的查询,存在空表或没有满足唯一索引条件的行的表。
  • No matching rows after partition pruning (JSON property: message)

    • 对于 DELETE 或 UPDATE,优化器在分区修剪后找不到任何可删除或更新的内容。 它的含义与 SELECT 语句的 Impossible WHERE 类似。
  • No tables used (JSON property: message)

    • 该查询没有 FROM 子句,或有 FROM DUAL 子句。
    • 对于 INSERT 或 REPLACE 语句,EXPLAIN 在没有 SELECT 部分时显示此值。 例如,它出现在 EXPLAIN INSERT INTO t VALUES(10) 中,因为它相当于 EXPLAIN INSERT INTO t SELECT 10 FROM DUAL。
  • Not exists (JSON property: 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(子查询)或 NOT EXISTS(子查询)形式的 WHERE 条件已在内部转换为反连接。 这会删除子查询并将其表带入最顶层查询的计划中,从而提供改进的成本规划。 通过合并半连接和反连接,优化器可以更自由地对执行计划中的表重新排序,在某些情况下会产生更快的计划。

    可以通过在执行 EXPLAIN 后检查 SHOW WARNINGS 中的 Message 列或在 EXPLAIN FORMAT=TREE 的输出中查看何时对给定查询执行反连接转换。

    反连接是半连接 table_a JOIN table_b ON 条件的补充。 反连接返回 table_a 中的所有行,而 table_b 中没有与条件匹配的行。

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

    • 当优化器尚未完成为在指定连接中执行的语句创建执行计划时,会与 EXPLAIN FOR CONNECTION 一起出现该值。 如果执行计划输出包含多行,则任何或所有行都可能具有此 Extra 值,具体取决于优化器确定完整执行计划的进度。
  • Range checked for each record (index map:N) (JSON property: message)

    • MySQL没有找到好的索引可以使用,但是发现在已知前面表中的列值之后可以使用某些索引。 对于前面表中的每个行组合,MySQL 检查是否可以使用 range 或 index_merge 访问方法来检索行。 这不是很快,但比执行完全没有索引的联接要快。
    • 索引从 1 开始编号,其顺序与表的 SHOW INDEX 显示的顺序相同。 索引映射值N是指示哪些索引是候选的位掩码值。 例如,值 0x19(二进制 11001)表示考虑索引 1、4 和 5。
  • Recursive (JSON property: recursive)

    • 这表明该行适用于递归公用表表达式的递归 SELECT 部分。
  • Rematerialize (JSON property: rematerialize)

    • Rematerialize (X,...) 显示在表 T 的 EXPLAIN 行中,其中 X 是任何横向派生表,当读取 T 的新行时会触发其重新实现。 例如:

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

      每次顶级查询处理 t 的新行时,都会重新具体化派生表的内容,以使其保持最新状态。

  • Scanned N databases (JSON property: message)

    • 这指示在处理 INFORMATION_SCHEMA 表的查询时服务器执行的目录扫描次数, N 的值可以是 0、1 或全部。
  • Select tables optimized away (JSON property: message)

    • 优化器确定 1) 最多应返回一行,2) 为了生成这一行,必须读取一组确定的行。 当要读取的行可以在优化阶段读取时(例如,通过读取索引行),则在查询执行期间不需要读取任何表。

    • 当查询被隐式分组(包含聚合函数但不包含 GROUP BY 子句)时,第一个条件得到满足。 当对每个使用的索引执行一次行查找时,就满足第二个条件。 读取的索引数决定了读取的行数。

    • 考虑以下隐式分组查询:

      SELECT MIN(c1), MIN(c2) FROM t1;

      假设可以通过读取一个索引行来检索 MIN(c1),并且可以通过从不同索引读取一行来检索 MIN(c2)。 也就是说,对于每一列 c1 和 c2,都存在一个索引,其中该列是索引的第一列。 在这种情况下,将返回一行,该行是通过读取两个确定性行而生成的。

    • 如果要读取的行不确定,则不会出现此额外值。 考虑这个查询:

      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),对于缺少 WHERE 子句或始终为 true 并且没有 GROUP BY 子句的 COUNT(*) 查询,可能会出现此额外值。 (这是隐式分组查询的一个实例,其中存储引擎影响是否可以读取确定数量的行。)

  • Skip_open_table, Open_frm_only, Open_full_table (JSON property: message)

    • 这些值指示适用于 INFORMATION_SCHEMA 表查询的文件打开优化。
      • Skip_open_table:不需要打开表文件。 该信息已经可以从数据字典中获得。
      • open_frm_only:表信息只需要读取数据字典。
      • open_full_table:未优化的信息查找。 表信息必须从数据字典和读取表文件中读取。
  • Start temporary, End temporary (JSON property: message)

    • 这表明临时表用于半连接重复清除策略。
  • unique row not found (JSON property: message)

    • 对于诸如 SELECT ... FROM tbl_name 之类的查询,没有行满足表上 UNIQUE 索引或 PRIMARY KEY 的条件。
  • Using filesort (JSON property: using_filesort)

    • MySQL 必须执行额外的操作来找出如何按排序顺序检索行。 排序是通过根据连接类型遍历所有行并存储排序键和指向与 WHERE 子句匹配的所有行的行的指针来完成的。 然后对键进行排序,并按排序顺序检索行。
  • Using index (JSON property: using_index)

    • 仅使用索引树中的信息从表中检索列信息,而无需执行额外的查找来读取实际行。 当查询仅使用属于单个索引的列时,可以使用此策略。
    • 对于具有用户定义的聚集索引的 InnoDB 表,即使 Extra 列中缺少“使用索引”,也可以使用该索引。 如果类型是索引并且键是主键,就是这种情况。
    • 有关所使用的任何覆盖索引的信息显示在 EXPLAINFORMAT=TRADITIONAL 和 EXPLAIN FORMAT=JSON 中。 EXPLAIN FORMAT=TREE 也会显示该信息。
  • Using index condition (JSON property: using_index_condition)

    • 通过访问索引元组并首先测试它们来确定是否读取完整的表行来读取表。 通过这种方式,索引信息用于推迟(“下推”)读取整个表行,除非有必要。
  • Using index for group-by (JSON property: using_index_for_group_by)

    • 与使用索引表访问方法类似,使用索引进行分组表示 MySQL 找到了一个可用于检索 GROUP BY 或 DISTINCT 查询的所有列的索引,而无需对实际表进行任何额外的磁盘访问。 此外,索引以最有效的方式使用,因此对于每个组,仅读取几个索引条目。
  • Using index for skip scan (JSON property: using_index_for_skip_scan)

    • 表示使用“跳过扫描”访问方法。
  • Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access), Using join buffer (hash join) (JSON property: using_join_buffer)

    • 来自早期连接的表被分部分读入连接缓冲区,然后使用缓冲区中的行来执行与当前表的连接。 (Block Nested Loop)表示使用Block Nested-Loop算法,(Batched Key Access)表示使用Batched Key Access算法,(hash join)表示使用散列连接。 也就是说,EXPLAIN 输出前一行的表中的键会被缓冲,并且从出现Using join buffer 的行表示的表中批量获取匹配的行。
    • 在 JSON 格式的输出中,using_join_buffer 的值始终是 Block Nested Loop、Batched Key Access 或 hash join 之一。
  • Using MRR (JSON property: message)

    • 使用多范围读取优化策略读取表。
  • Using sort_union(...), Using union(...), Using intersect(...) (JSON property: message)

    • 这些指示显示如何针对index_merge 连接类型合并索引扫描的特定算法。
  • Using temporary (JSON property: using_temporary_table)

    • 为了解决查询,MySQL 需要创建一个临时表来保存结果。 如果查询包含以不同方式列出列的 GROUP BY 和 ORDER BY 子句,通常会发生这种情况。
  • Using where (JSON property: attached_condition)

    • WHERE 子句用于限制哪些行与下一个表匹配或发送到客户端。 除非您特别打算从表中获取或检查所有行,否则如果 Extra 值不是“Using where”并且表连接类型是“ALL”或“索引”,则查询中可能会出现问题。
    • 使用 where 在 JSON 格式的输出中没有直接对应项; Attached_condition 属性包含使用的任何 WHERE 条件。
  • Using where with pushed condition (JSON property: message)

    • 此项仅适用于 NDB 表。 这意味着 NDB Cluster 正在使用条件下推优化来提高非索引列和常量之间直接比较的效率。 在这种情况下,条件被“下推”到集群的数据节点,并同时在所有数据节点上进行评估。 这消除了通过网络发送不匹配行的需要,并且与可以使用条件下推但未使用的情况相比,可以将此类查询速度提高 5 到 10 倍。
  • Zero limit (JSON property: message)

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

EXPLAIN 输出解释

通过计算 EXPLAIN 输出的 rows 列中的值的乘积,可以很好地了解连接的好坏。 这应该大致表示 MySQL 必须检查多少行才能执行查询。 如果使用 max_join_size 系统变量限制查询,则此行积还用于确定要执行哪些多表 SELECT 语句以及要中止哪些语句。

以下示例展示了如何根据 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;

对于此示例,请做出以下假设:

所比较的列已声明如下:

TableColumnData Type
ttActualPCCHAR(10)
ttAssignedPCCHAR(10)
ttClientIDCHAR(10)
etEMPLOYIDCHAR(15)
doCUSTNMBRCHAR(15)

这些表具有以下索引:

TableIndex
ttActualPC
ttAssignedPC
ttClientID
etEMPLOYID (primary key)
doCUSTNMBR (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)

因为每个表的类型都是 ALL,所以此输出表明 MySQL 正在生成所有表的笛卡尔积; 也就是说,行的每种组合。 这需要相当长的时间,因为必须检查每个表中行数的乘积。 对于当前的情况,该乘积为 74 × 2135 × 74 × 3872 = 45,268,558,720 行。 如果table更大,你可以想象需要多长时间。

这里的一个问题是,如果将列声明为相同的类型和大小,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

这并不完美,但要好得多:行值的乘积减少了 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 表的情况并非如此。 幸运的是,让 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 连接优化器的有根据的猜测。 通过将行乘积与查询返回的实际行数进行比较,检查数字是否接近事实。 如果数字相差很大,则通过在 SELECT 语句中使用 STRAIGHT_JOIN 并尝试在 FROM 子句中以不同的顺序列出表,可能会获得更好的性能。 (但是,STRAIGHT_JOIN 可能会阻止使用索引,因为它禁用半连接转换。请参阅使用半连接转换优化 IN 和 EXISTS 子查询谓词。)

在某些情况下,当 EXPLAIN SELECT 与子查询一起使用时,可能会执行修改数据的语句。