前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【MySQL 文档翻译】理解查询计划

【MySQL 文档翻译】理解查询计划

作者头像
tuhooo
修改2022-07-10 14:53:02
2.1K0
修改2022-07-10 14:53:02
举报
文章被收录于专栏:乐乐乐乐

原文地址: 【MySQL 文档翻译】理解查询计划

欢迎访问我的博客: http://blog.duhbb.com/

官方文档

MySQL 官方文档地址: 8.8 Understanding the Query Execution Plan

引言

MySQL 优化器会根据 SQL 语句中的表, 列, 索引和 WHERE 子句中的条件的详细信息, 使用许多技术来有效地执行 SQL 查询. 可以在不读取所有行的情况下对一个巨大的表执行查询; 可以在不比较每个行组合的情况下执行涉及多个表的连接. 优化器选择执行最有效查询的一组操作称为 查询执行计划 (query execution plan), 也称为 EXPLAIN plan. 你的目标是认识到 EXPLAIN 计划表明查询已优化好, 如果发现一些低效的操作, 可以通过学习 SQL 语法和索引技术来改进查询计划.

使用 EXPLAIN 优化查询

EXPLAIN 语句提供有关 MySQL 如何执行目标语句的信息:

  • EXPLAIN 可以与 SELECT, DELETE, INSERT, REPLACE 和 UPDATE 语句一起使用.
  • 当 EXPLAIN 与可解释语句 (explainable statement) 一起使用时, MySQL 会显示来自优化器的有关语句执行计划的信息. 也就是说, MySQL 解释了它将如何处理该语句, 包括有关表 如何连接 以及以 何种顺序 连接的信息. 有关使用 EXPLAIN 获取执行计划信息的信息, 请参阅第 8.8.2 节 EXPLAIN 输出格式.
  • 当 EXPLAIN 与 FOR CONNECTION connection_id 而不是可解释的语句一起使用时, 它显示在命名连接中执行的语句的执行计划. 请参阅第 8.8.4 节 获取命名连接的执行计划信息.
  • 对于 SELECT 语句, 使用 SHOW WARNINGS 可是使 EXPLAIN 生成并显示的附加执行计划信息. 请参阅第 8.8.3 节 扩展 EXPLAIN 输出格式.
  • EXPLAIN 对于检查涉及分区表的查询很有用. 请参阅第 24.3.5 节 获取有关分区的信息.
  • FORMAT 选项可用于选择输出格式. TRADITIONAL 以表格格式显示输出. 如果没有 FORMAT 选项, 这是默认设置. 当 FORMAT 的选项值为 JSON 可以显示 JSON 格式的信息.

在 EXPLAIN 的帮助下, 可以查看应该在哪里为表添加索引, 以便通过使用索引查找行来更快地执行语句. 您还可以使用 EXPLAIN 检查优化器是否以最佳顺序连接表. 要提示优化器使用与语句中表命名顺序相对应的连接顺序, 请以 SELECT STRAIGHT_JOIN 语句开头, 而不是 SELECT. (请参阅 第 13.2.10 节 SELECT 语句.) 但是, STRAIGHT_JOIN 可能会阻止使用索引, 因为它禁用了 半连接转换. 看第 8.2.2.1 节 使用半连接转换优化 IN 和 EXISTS 子查询谓词.

优化器跟踪 有时可能会提供比 EXPLAIN 更详细的信息. 但是, 优化器跟踪格式和内容可能会因版本而异. 有关详细信息, 请参阅 MySQL 内部: 跟踪优化器.

如果您认为应该用到的索引在查询计划中确没有被使用, 请运行 ANALYZE TABLE 以更新表统计信息, 例如键的基数, 这可能会影响优化器所做的选择. 请参阅第 13.7.3.1 节 ANALYZE TABLE 语句.

Explain 的输出格式

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

Explain 输出的列

本节介绍由 EXPLAIN 生成的输出列. 后面的部分提供了有关 type 和 Extra 列的附加信息.

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

JSON 名称

含义

id

select_id

SELECT 标识符

select_type

None

SELECT 类型

table

table_name

输出行的表

partitions

partitions

匹配的分区

type

access_type

联接 (join) 类型

possible_keys

possible_keys

可供选择的索引

key

key

实际选择的索引

key_len

key_length

所选 key 的长度

ref

ref

与索引比较的列

rows

rows

估计要检查的行数

filtered

filtered

按表条件过滤的行百分比 (过滤得越多说明效果越好)

Extra

None

附加信息

JSON 格式的输出中的 NULL 不会在 JSON 格式的 EXPLAIN 信息中显示.

  • id SELECT 标识符. 这是查询中 SELECT 的序号. 如果该值是 NULL 则表示该行引用其他行的联合结果 (union result). 在这种情况下, table 列显示的是 <unionM,N> , 表示该行是 M 和 N 在 id 上联合 (union) 的行.
  • select_type SELECT 的类型, 可以是下表中显示的任何一种.

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 一般指关联子查询的使用. 详情见 Section 13.2.11.7 Correlated Subqueries

DEPENDENT SUBQUERY 和计算和 UNCACHEABLE SUBQUERY 的计算不太一样.DEPENDENT SUBQUERY 中的子查询对外部上下文中的不同集合的值只计算一遍. 而 UNCACHEABLE SUBQUERY, 会对外部上下文中的每一行都重新计算.

  • table 输出行所引用的表的名称. 这也可以是以下值之一: - <union M,N>: 该行是指具有 M 和 N 并. - <derived N>: 该行是指值为 N 的派生表结果. 例如, 派生表可能来自 FROM 子句中的子查询. - <subquery N>: 该行指的是 N 的物化子查询的结果. 请参阅第 8.2.2.2 节 使用物化优化子查询.
  • partitions 查询将匹配记录的分区. 如果该值为 NULL 则表明是非分区表.
  • type 联接类型. 有关不同类型的描述, 请参阅 EXPLAIN 连接类型. 联接类型还挺重要的.
  • possible_keys (重要) 该 possible_keys 列指示 MySQL 可以选择从中查找此表中的行的索引. 请注意, 此列完全独立于输出中显示的表格顺序 EXPLAIN. 这意味着某些键在 possible_keys 实际中可能无法与生成的表顺序一起使用. 如果此列是 NULL(或在 JSON 格式的输出中未定义), 则没有相关索引. 在这种情况下, 您可以通过检查 WHERE 子句来检查它是否引用了适合索引的某些列或列, 从而提高查询的性能. 如果是这样, 请创建一个适当的索引并 EXPLAIN 再次检查查询. 要查看表有哪些索引, 请使用: SHOW INDEX FROM tbl_name
  • key (重要) 该 key 列指示 MySQL 实际决定使用的键 (索引). 如果 MySQL 决定使用其中一个 possible_keys 索引来查找行, 则该索引被列为键值. 可能 key 中的值中不存在的索引 possible_keys 中. 如果 possible_keys 没有合适的索引进行查询则会出现这种情况, 但查询选择的所有列都是其他索引的列, 则可能会发生这种情况. 也就是说, 命名索引覆盖了选定的列, 因此虽然它不用于确定要检索哪些行, 但索引扫描比数据行扫描更有效. (感觉就是查询的时候, 所有的索引都不太可用, 然后就走全表扫描, 但是所选的列又是被二级索引覆盖了, 所以会优先去扫描二级索引, 我理解的是这样的) 对于 InnoDB, 即使查询还选择了主键, 二级索引也可能覆盖选定的列, 因为 InnoDB 每个二级索引都存储了主键值. 如果 key 是 NULL, 则 MySQL 没有找到可用于更有效地执行查询的索引. 要强制 MySQL 使用或忽略 possible_keys 列中列出的索引, 请在查询中使用 FORCE INDEX, USE INDEXIGNORE INDEX. 对于 MyISAM 表, 运行 ANALYZE TABLE 有助于优化器选择更好的索引. 对于 MyISAM 表, myisamchk --analyze 也是如此.
  • key_len key_len 列指示 MySQL 决定使用的 key 的长度. key_len 的值使您能够确定 MySQL 实际使用的 multi-part key 的多少部分. 如果 key 是 NULL, key_len 也是 NULL. 由于 key 存储格式的原因, 可以为空的 key 的长度要不不能为空的 key 的长度多 1. (这个应该是联合索引可以用到前几个的情况)
  • ref ref 列显示将哪些列或常量与 key 列中指定的索引进行比较以从表中选择行. 如果值为 func, 则使用的值是某个函数的结果. 要查看哪个功能, 请在 EXPLAIN 后使用 SHOW WARNINGS 以 EXPLAIN 扩展输出. 该函数实际上可能是一个运算符, 例如算术运算符.
  • rows rows 列指示 MySQL 认为它必须检查以执行查询的行数. 对于 InnoDB 表格, 这个数字是一个估计值, 可能并不总是准确的.
  • filtered filtered 列指示按表条件过滤的表行的估计百分比. 最大值为 100, 这意味着没有过滤行. 从 100 开始减小的值表示过滤量增加. rows 显示检查的估计行数, $rows × filtered$ 显示与下表连接的行数. 例如, 如果 rows 是 1000 并且 filtered 是 50.00 (50%), 则要与下表连接的行数是 $1000 × 50% = 500$.
  • Extra 此列包含有关 MySQL 如何解析查询的附加信息. 有关不同值的描述, 请参阅 EXPLAIN 额外信息.

解释连接类型

EXPLAIN 输出中的 type 列描述了表是如何连接的. 在 JSON 格式的输出中, 则放到了 access_type 属性. 以下列表描述了连接类型, 按从最佳到最差的顺序排列:

  • system 该表只有一行 (= 系统表). 这是 const 连接类型的一个特例.
  • const 该表最多有一个匹配行, 在查询开始时读取. 因为只有一行, 所以这一行中列的值可以被优化器的其余部分视为常量. const 表非常快, 因为它们只被读取一次. 当您将 PRIMARY KEY 或 UNIQUE 索引的所有部分与常量值进行比较时就是使用 const. 在以下查询中, tbl_name 可以用作 const 表:
代码语言:sql
复制
    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:
代码语言:sql
复制
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 索引) 对于先前表中的每个行组合, 从该表中读取具有匹配索引值的所有行. 如果连接仅使用键的最左前缀或键不是 PRIMARY KEY 或 UNIQUE 索引 (换句话说, 如果连接不能基于键值选择单行), 则是 ref. 如果使用的键只匹配几行, 这是一个很好的连接类型. ref 可用于使用 =<=> 运算符比较的索引列. 在以下示例中, MySQL 可以使用 ref 连接来处理 ref_table:`sql SELECT * FROM ref_table WHERE key_column=expr;
代码语言:txt
复制
SELECT * FROM ref_table,other_table
代码语言:txt
复制
  WHERE ref_table.key_column=other_table.column;
代码语言:txt
复制
SELECT * FROM ref_table,other_table
代码语言:txt
复制
  WHERE ref_table.key_column_part1=other_table.column
代码语言:txt
复制
  AND ref_table.key_column_part2=1;
代码语言:txt
复制
代码语言:txt
复制
- fulltext
连接是使用 FULLTEXT 索引执行的.
- ref\_or\_null(相比 `ref` 加了一个可以为 null)
这种连接类型类似于 ref, 但另外 MySQL 会额外搜索包含 NULL 值的行. 这种连接类型优化最常用于解析子查询. 在以下示例中, MySQL 可以使用 ref\_or\_null 连接来处理 ref\_table:```sql
SELECT \* FROM ref\_table
  WHERE key\_column=expr OR key\_column IS NULL;
```请参阅[第 8.2.1.15 节 IS NULL 优化](https://dev.mysql.com/doc/refman/8.0/en/is-null-optimization.html).
代码语言:txt
复制
- index\_merge
此连接类型表明使用了索引合并优化. 在这种情况下, key 输出行中的列包含所用索引的列表, 并 key\_len 包含所用索引的最长键部分的列表. 有关更多信息, 请参阅[第 8.2.1.3 节 索引合并优化](https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html).- index\\_subquery
此连接类型类似于 unique\\_subquery. 它替换 IN 子查询, 但它适用于以下形式的子查询中的非唯一索引:```sql
value IN (SELECT key\\_column FROM single\\_table WHERE some\\_expr)
- unique\_subquery
此类型可将通过下面的 IN 子查询替换 eq\_ref:`sql
value IN (SELECT primary\_key FROM single\_table WHERE some\_expr)
- range
仅检索给定范围内的行, 使用索引选择行. 输出行中的 key 列指示使用了哪个索引. key\_len 包含使用的最长的关键部分. 该 ref 列适用 NULL 于这种类型.
range 可以在使用运算符中的任何一个与常量进行比较:`sql
SELECT \* FROM tbl\_name
  WHERE key\_column = 10;

SELECT * FROM tbl_name

代码语言:txt
复制

WHERE key_column BETWEEN 10 and 20;

代码语言:txt
复制

SELECT * FROM tbl_name

代码语言:txt
复制

WHERE key_column IN (10,20,30);

代码语言:txt
复制

SELECT * FROM tbl_name

代码语言:txt
复制

WHERE key_part1 = 10 AND key_part2 IN (10,20,30);

代码语言:txt
复制
代码语言:txt
复制
  • index (完全扫描索引了) 连接类型与 index 相同 ALL, 只是扫描了索引树. 这有两种方式: - 如果索引是查询的覆盖索引并且可以用于满足表中所需的所有数据, 则仅扫描索引树. 在这种情况下, 该 Extra 列 显示 Using index. 仅索引扫描通常比仅索引扫描更快, ALL 因为索引的大小通常小于表数据. - 使用从索引中读取以按索引顺序查找数据行来执行全表扫描. Uses index 没有出现在 Extra 列中. 当查询仅使用属于单个索引的列时,MySQL 可以使用此连接类型.
  • ALL(全表扫描了) 对先前表中的每个行组合进行全表扫描. 如果该表是第一个未被标记为 const 的表 , 这通常不好, 并且在所有其他情况下通常非常糟糕. 通常, 您可以 ALL 通过添加索引来避免基于先前表中的常量值或列值从表中检索行.

哇偶, 好厉害, 感觉这个得掌握一下哦

解释额外信息

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

如果您想尽可能快地进行查询, 请注意查看 Extra 值是否包含 Using filesortUsing 还是 Using temporary.

Extra 提供了更多的信息, 比如是否用了临时表, 是否走了文件排序

  • Backward index scan 优化器能够对 InnoDB 表使用降序索引. 与 Using index 一起显示. 有关更多信息, 请参阅第 8.3.13 节 降序索引.
  • Child of 'table' pushed join@1(不重要) 该表被引用为 table 可以下推到 NDB 内核的连接中的子项. 仅在启用下推连接时适用于 NDB Cluster.ndb_join_pushdown.
  • const row not found 对于诸如 SELECT ... FROM tbl_name 的查询, 表是空的.
  • Deleting all rows(删除的时候是否走捷径?) 对于 DELETE, 一些存储引擎 (例如 MyISAM) 支持一种处理程序方法, 该方法以简单快速的方式删除所有表行. 如果引擎使用此优化, Extra 则会显示此值.
  • Distinct(是否 distinct?) MySQL 正在寻找不同的值, 因此它在找到第一个匹配行后停止为当前行组合搜索更多行.
  • FirstMatch(tbl_name) 对 tble_name 这个表使用了 semijoin FirstMatch 连接快捷策略.
  • Full scan on NULL key 当优化器无法使用索引查找访问方法时, 子查询优化会发生这种情况作为回退策略.
  • Impossible HAVING HAVING 子句始终为 false, 不能选择任何行.
  • Impossible WHERE WHERE 子句始终为 false, 不能选择任何行.
  • Impossible WHERE noticed after reading const tables MySQL 已读取所有 const(and system) 表并注意到该 WHERE 子句始终为 false.
  • LooseScan(m..n) 使用半连接 LooseScan 策略. m 并且 n 是关键部件号.
  • No matching min/max row 没有行满足查询的条件, 例如: SELECT MIN(...) FROM ... WHERE condition
  • no matching row in const table 对于带有连接的查询, 有一个空表或没有满足唯一索引条件的行的表.
  • No matching rows after partition pruning 对于 DELETE or UPDATE, 优化器在分区修剪后没有发现要删除或更新的内容. 它与 SELECT 语句 Impossible WHERE 的含义相似.
  • No tables used 查询没有 FROM 子句, 或有 FROM DUAL 子句. 对于 INSERT 或 REPLACE 语句, 当没有 SELECT 部分时 EXPLAIN 显示此值. 执行 EXPLAIN INSERT INTO t SELECT 10 FROM DUAL 会显示这个.
  • Not exists MySQL 能够对 LEFT JOIN 查询进行优化, 并且在找到与条件匹配的行后, 不会检查该表中的前一行组合的更多行. 以下是可以通过这种方式优化的查询类型的示例:sql SELECT \* FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;假设 t2.id 定义为 NOT NULL. 在这种情况下, MySQL 扫描 t1 并查找行 t2 中与 t1.id 值相等的行. 如果 MySQL 在 t2 中找到匹配的行 , 它就知道 t2.id 永远不可能为 NULL, 并且不会扫描具有相同值的其余行. 换句话说, 对于 t1 中的每一行, MySQL 只需要在 t2 中进行一次查找, 而不管在 中实际匹配了多少行.

在 MySQL 8.0.17 及更高版本中, 这也可以指示 WHERE 中的 NOT IN (subquery)NOT EXIST (subquery) 已在内部转换为反连接 (antijoin). 这将移除子查询并将其表带入最顶层查询的计划中, 从而提供改进的成本计划. 通过合并半连接 (semijoins) 和反连接 (antijoins), 优化器可以更自由地重新排序执行计划中的表, 在某些情况下会产生更快的计划.(牛逼了)

您可以通过 EXPLAIN 后执行 SHOW WARNINGS 后的 Message 列或在 EXPLAIN FORMAT=TREE 的输出中查看对给定查询是否执行反连接转换.> 笔记

代码语言:txt
复制
反连接是半连接 table_a 和 table_b 在 condition 上的补充. 反连接返回 table_a 中所有没有在 condition 上匹配 table_b 的所有行.
  • Plan isn't ready yet EXPLAIN FOR CONNECTION 当优化器尚未完成为在命名连接中执行的语句创建执行计划时, 会出现此值. 如果执行计划输出包含多行, 则任何或所有行都可能具有此 Extra 值, 具体取决于优化器在确定完整执行计划时的进度.- Scanned N databases 这表示在处理表查询时服务器执行了多少目录扫描 INFORMATION\_SCHEMA, 如第 8.2.3 节 优化 INFORMATION\_SCHEMA 查询 中所述. N 的值可以是 0, 1 或 all. 如果要读取的行不确定, 则 Extra 不会出现此值. 考虑这个查询:```sql SELECT MIN(c2) FROM t1 WHERE c1 < = 10; ```假设这 (c1, c2) 是一个覆盖索引. 使用此索引, c1 <= 10 必须扫描所有行以找到最小值 c2. 相比之下, 考虑这个查询:```sql SELECT MIN(c2) FROM t1 WHERE c1 = 10; ```在这种情况下, 第一个索引行 c1 = 10 包含最小值 c2 . 只需读取一行即可生成返回的行. 对于维护每个表的精确行数的存储引擎 (例如 MyISAM, 但不是 InnoDB), 对于缺少 `WHERE` 子句的 `COUNT(\*)` 或始终为真且没有 `GROUP BY` 子句的查询, Extra 可能会出现此值 .(这是一个隐式分组查询的实例, 其中存储引擎影响是否可以读取确定的行数.) - Skip\\_open\\_table, Open\\_frm\\_only, Open\\_full\\_table 这些值表示适用于 INFORMATION\\_SCHEMA 表查询的文件打开优化. - Skip\\_open\\_table: 表格文件不需要打开. 该信息已从数据字典中获得. - Open\\_frm\\_only: 表信息只需要读取数据字典. - Open\\_full\\_table: 未优化的信息查找. 表信息必须从数据字典中读取并通过读取表文件. - Start temporary, End temporary 这表明临时表用于 semijoin Duplicate Weedout 策略. - unique row not found 对于诸如 `SELECT ... FROM tbl\_name` 的查询, 没有行满足 UNIQUE 索引或 PRIMARY KEY. - Using filesort(重要) MySQL 必须做一个额外的过程来找出如何按排序顺序检索行. 排序是通过根据连接类型遍历所有行并存储排序键和指向与 WHERE 子句匹配的所有行的行的指针来完成的. 然后对键进行排序, 并按排序顺序检索行. 请参见[第 8.2.1.16 节 排序优化](https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html). - Using index 仅使用索引树中的信息从表中检索列信息, 而无需执行额外的查找来读取实际行. 当查询仅使用属于单个索引的列时, 可以使用此策略. 对于 InnoDB 具有用户定义的聚集索引的表, 即使 Extra 这一列中不存在 Using index 也可以使用该索引 Extra. 如果 type 是 index 而 key PRIMARY 就是这种情况. - Using index condition 通过访问索引元组并首先对其进行测试以确定是否读取完整的表行来读取表. 这样, 除非有必要, 否则索引信息用于延迟 ("下推") 读取全表行. 请参阅[第 8.2.1.6 节 索引条件下推优化](https://dev.mysql.com/doc/refman/8.0/en/index-condition-pushdown-optimization.html). - Using index for group-by 与 Using index 表访问方法类似, Using index for group-by 表明 MySQL 找到了一个索引, 该索引可用于检索 GROUP BY 或 DISTINCT 查询的所有列, 而无需对实际表进行任何额外的磁盘访问. 此外, 索引以最有效的方式使用, 因此对于每个组, 只读取几个索引条目. 有关详细信息, 请参阅[第 8.2.1.17 节 GROUP BY 优化](https://dev.mysql.com/doc/refman/8.0/en/group-by-optimization.html). - Using index for skip scan 表示使用了 `跳过扫描访问 (Skip Scan)` 方法. 请参阅[跳过扫描范围访问方法](https://dev.mysql.com/doc/refman/8.0/en/range-optimization.html#range-access-skip-scan). - Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access), Using join buffer (hash join) 来自早期连接的表被部分读入连接缓冲区, 然后从缓冲区中使用它们的行来执行与当前表的连接. (Block Nested Loop) 指示使用块嵌套循环算法,(Batched Key Access) 指示使用批量 key 访问算法, (hash join) 表示使用散列连接. 也就是说, EXPLAIN 输出前一行的表中的键被缓冲, 匹配的行从出现的行所代表的表中批量提取, 显示 `Using join buffer`. 在 JSON 格式的输出中, 的值 using\\_join\\_buffer 始终是 Block Nested Loop,Batched Key Access 或之一 hash join. 哈希连接从 MySQL 8.0.18 开始可用; 在 MySQL 8.0.20 或更高版本的 MySQL 中不使用 Block Nested-Loop 算法. 有关这些优化的更多信息, 请参阅第 8.2.1.4 节,"哈希连接优化"和 块嵌套循环连接算法. 有关批量密钥访问算法的信息, 请参阅批量密钥访问连接. - Using MRR 使用多范围读取优化策略读取表. 请参见[第 8.2.1.11 节 多范围读取优化](https://dev.mysql.com/doc/refman/8.0/en/mrr-optimization.html). - Using sort\\_union(...), Using union(...), Using intersect(...) 这些指示显示了如何为 index\\_merge 连接类型索引扫描的特定算法. 请参阅[第 8.2.1.3 节 索引合并优化](https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html). - Using temporary 为了解析查询, MySQL 需要创建一个临时表来保存结果. 如果查询包含以不同方式列出列的 `GROUP BY` 和 `ORDER BY` 子句, 通常会发生这种情况. - Using where WHERE 子句用于限制与下一个表匹配或发送到客户端的行. 除非您特别打算从表中获取或检查所有行, 否则如果 Extra 值不是 Using where 并且表连接类型是 `ALL` 或 `index`, 则您的查询可能有问题. - Using where with pushed condition 此项仅适用于 NDB 表. - Zero limit 该查询有一个 LIMIT 0 子句, 不能选择任何行. - Range checked for each record (index map: N) MySQL 没有找到可以使用的好的索引, 但发现某些索引可能会在之前表中的列值已知后使用. 对于前面表格中的每个行组合, MySQL 检查是否可以使用 range 或者 index\_merge 访问方法来检索行. 这不是很快, 但比执行完全没有索引的连接要快. 应用的准则在 [第 8.2.1.2 节 范围优化](https://dev.mysql.com/doc/refman/8.0/en/range-optimization.html) 和[第 8.2.1.3 节 索引合并优化](https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html) 中所述, 但上表的所有列值都是已知的并被视为常量. 索引从 1 开始编号, 顺序与表中所示的相同 SHOW INDEX. 索引映射值 N 是指示哪些索引是候选索引的位掩码值. 例如, 值 0x19(二进制 11001) 表示考虑索引 1,4 和 5. - Recursive 这表明该行适用于 SELECT 递归公用表表达式的递归部分. 请参见[第 13.2.15 节 WITH(公用表表达式)](https://dev.mysql.com/doc/refman/8.0/en/with.html). - Rematerialize `Rematerialize (X,...)` 显示在 `EXPLAIN T` 中, 其中是在读取 X 新行时触发重新实现的任何横向派生表. 例如:`sql SELECT ... FROM t, LATERAL (derived table that refers to t) AS dt ... ### EXPLAIN 输出解释 通过获取 EXPLAIN 输出 rows 列中值的乘积, 您可以很好地了解连接的好坏. 这应该大致告诉您 MySQL 必须检查多少行才能执行查询. 如果您使用 `max_join_size` 系统变量限制查询, 则此行积还用于确定 SELECT 要执行哪些多表语句以及要中止哪些语句. 请参见[第 5.1.1 节 配置服务器](https://dev.mysql.com/doc/refman/8.0/en/server-configuration.html). 以下示例显示了如何根据 EXPLAIN 提供的信息逐步优化多表连接. 假设您有如下的 SELECT 语句, 并且您计划使用 EXPLAIN 命令对其进行检查 : ```sql 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;对于此示例, 作出以下假设:表列数据类型ttActualPCCHAR(10)ttAssignedPCCHAR(10)ttClientIDCHAR(10)etEMPLOYIDCHAR(15)doCUSTNMBRCHAR(15)表索引ttActualPCttAssignedPCttClientIDetEMPLOYID(主键)doCUSTNMBR(主键)最初, 在执行任何优化之前, 该 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 可以更有效地使用列上的索引. 在这种情况下, 如果 VARCHAR 和 CHAR 被声明为相同的大小, 则认为它们是相同的.tt.ActualPC 被声明为 CHAR(10) , 而 et.EMPLOYID 被声明为 CHAR(15), 因此存在长度不匹配.要修复列长度之间的这种差异, 请使用 ALTER TABLE 将 ActualPC 从 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 分析 key 分布很容易: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 1EXPLAIN 输出中的 rows 列是来自 MySQL 连接优化器的有根据的猜测. rows 通过将产品与查询返回的实际行数进行比较, 检查这些数字是否更接近事实. 如果数字完全不同, 您可能会通过 STRAIGHT_JOINSELECT 语句中使用并尝试在 FROM 子句中以不同的顺序列出表来获得更好的性能.(但是, STRAIGHT_JOIN 可能会阻止使用索引, 因为它禁用了 半连接转换. 请参阅第 8.2.2.1 节 使用半连接转换优化 IN 和 EXISTS 子查询谓词.在某些情况下, 当 EXPLAIN SELECT 与子查询一起使用时, 可以执行修改数据的语句; 有关更多信息, 请参阅第 13.2.11.8 节 派生表.扩展 EXPLAIN 输出格式该 EXPLAIN 语句产生额外的信息, 这些信息不是 EXPLAIN 输出的一部分, 但可以通过在 EXPLAIN 后接着 SHOW WARNINGS 语句来查看. 从 MySQL 8.0.12 开始, 扩展信息可用于 SELECT, DELETE, INSERT, REPLACE 和 UPDATE 语句. 在 8.0.12 之前, 扩展信息仅适用于 SELECT 语句.SHOW WARNINGS 输出的 Message 显示优化器在 SELECT 语句如何限定表名和列名, SELECT 应用重写和优化规则后的样子, 以及可能有关优化过程的其他注释.EXPLAIN 后的 SHOW WARNINGS 仅针对 SELECT 语句生成扩展信息. 其他可解释语句 (DELETE, INSERT, REPLACE 和 UPDATE) 则显示的空结果.这是扩展 EXPLAIN 输出的示例:mysql> EXPLAIN SELECT t1.a, t1.a IN (SELECT t2.a FROM t2) FROM t1\G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 type: index possible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 4 filtered: 100.00 Extra: Using index *************************** 2. row *************************** id: 2 select_type: SUBQUERY table: t2 type: index possible_keys: a key: a key_len: 5 ref: NULL rows: 3 filtered: 100.00 Extra: Using index 2 rows in set, 1 warning (0.00 sec)
  • Select tables optimized away 优化器确定 1) 最多应该返回一行, 以及 2) 要生成这一行, 必须读取一组确定性的行. 当在优化阶段可以读取要读取的行时 (例如, 通过读取索引行), 在查询执行期间不需要读取任何表. 当查询被隐式分组 (包含聚合函数但没有 GROUP BY 子句) 时, 第一个条件得到满足. 当每个使用的索引执行一次行查找时, 满足第二个条件. 读取的索引数决定了要读取的行数. 考虑以下隐式分组查询:`sql SELECT MIN(c1), MIN(c2) FROM t1;
  • 被比较的列已声明如下.
  • 这些表具有以下索引.
  • 这些 tt.ActualPC 值不是均匀分布的.

mysql> SHOW WARNINGS\G

*** 1. row ***

Level: Note

Code: 1003

Message: / select#1 / select test .t1 .a AS a ,

代码语言:txt
复制
     < in_optimizer> (` test` .` t1` .` a` ,` test` .` t1` .` a` in
代码语言:txt
复制
     ( < materialize> (/* select#2 */ select ` test` .` t2` .` a`
代码语言:txt
复制
     from ` test` .` t2` where 1 having 1 ),
代码语言:txt
复制
     < primary_index_lookup> (` test` .` t1` .` a` in
代码语言:txt
复制
     < temporary table> on < auto_key>
代码语言:txt
复制
     where ((` test` .` t1` .` a` = ` materialized-subquery` .` a` ))))) AS ` t1.a
代码语言:txt
复制
     IN (SELECT t2.a FROM t2)` from ` test` .` t1`

1 row in set (0.00 sec)

代码语言:txt
复制

因为显示的语句 SHOW WARNINGS 可能包含特殊标记以提供有关查询重写或优化器操作的信息, 所以该语句不一定是有效的 SQL, 并且不打算执行. 输出还可能包含带有 Message 值的行, 这些值提供有关优化器所采取的操作的附加非 SQL 解释性说明.

以下列表描述了可以出现在由 SHOW WARNINGS 显示的扩展输出中的特殊标记:

  • <auto_key> 为临时表自动生成的键.
  • <cache>(expr) 表达式 (例如标量子查询) 执行一次, 结果值保存在内存中供以后使用. 对于由多个值组成的结果, 可以创建一个临时表并 <temporary table> 改为显示.
  • <exists>(query fragment) 将子查询谓词转换为 EXISTS 谓词, 并对子查询进行转换, 以便它可以与 EXISTS 谓词一起使用.
  • <in_optimizer>(query fragment) 这是一个没有用户意义的内部优化器对象.
  • <index_lookup>(query fragment) 使用索引查找来处理查询片段以查找符合条件的行.
  • <if>(condition, expr1, expr2) 如果条件为真, 则计算为 expr1, 否则 为 expr2.
  • <is_not_null_test>(expr) 验证表达式不为 NULL.
  • <materialize>(query fragment) 使用了子查询的物化.
  • materialized-subquery.col_name A reference to the column col_name in an internal temporary table materialized to hold the result from evaluating a subquery.
  • <primary_index_lookup>(query fragment) 使用主键查找来处理查询片段以查找符合条件的行.
  • <ref_null_helper>(expr) 这是一个没有用户意义的内部优化器对象.
  • /* select#N */ select_stmt SELECT 与非扩展 EXPLAIN 输出中 N 的 id 值相关联.
  • outer_tables semi join (inner_tables) 半连接操作. inner_tables 显示未拉出的表. 请参阅第 8.2.2.1 节,"使用半连接转换优化 IN 和 EXISTS 子查询谓词".
  • <temporary table> 这表示为缓存中间结果而创建的内部临时表.

当某些表属于 constsystem 类型时, 涉及这些表中的列的表达式由优化器提前求值, 而不是显示语句的一部分. 但是, 使用 FORMAT=JSON 时, 某些 const 表访问会显示为使用 const 值的 ref 访问.

获取命名连接的执行计划信息

暂时用不上, 这部分地址: 获取命名连接的执行计划信息

估计查询性能

在大多数情况下, 您可以通过计算磁盘寻道 (disk seeks) 次数来估计查询性能. 对于小型表, 通常可以在一次磁盘查找中找到一行 (因为索引可能已缓存). 对于更大的表, 您可以估计, 使用 B-tree 索引, 您需要这么多次查找才能找到一行:

$$

\frac{log(row\_count)}{log(index\_block\_length / 3 * 2 / (index\_length + data\_pointer\_length))} + 1

$$

在 MySQL 中, 索引块通常为 1024 字节, 数据指针通常为 4 字节. 对于一个 500000 行的表, 键值长度为 3 个字节 (大小为 MEDIUMINT), 公式表示 $log(500000)/log(1024/3*2/(3+4)) + 1= 4$ seeks.

该索引需要大约 $500000 * 7 * 3/2 = 5.2MB$ 的存储空间 (假设典型的索引缓冲区填充率为 2/3), 因此您可能在内存中有很多索引, 因此只需要一两次调用读取数据以查找行.

但是, 对于写入, 您需要四个查找请求来查找放置新索引值的位置, 通常需要两次查找来更新索引并写入行.

前面的讨论并不意味着您的应用程序性能会以 $log(N)$ 的速度缓慢下降. 只要一切都被操作系统或 MySQL 服务器缓存, 随着表变大, 事情只会稍微变慢. 在数据变得太大而无法缓存后, 事情开始变得慢得多, 直到您的应用程序仅受磁盘搜索 (增加 log N) 的约束. 为避免这种情况, 请随着数据的增长而增加 key 的缓存大小. 对于 MyISAM 表, 键缓存大小由 key_buffer_size 系统变量控制. 请参见第 5.1.1 节 配置服务器.

笔者的验证 Demo

没有刻意增加 Explain 的使用 Demo, 后续的开发中会找机会验证的.

原文地址: 【MySQL 文档翻译】理解查询计划

欢迎访问我的博客: http://blog.duhbb.com/

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 官方文档
  • 引言
  • 使用 EXPLAIN 优化查询
  • Explain 的输出格式
    • Explain 输出的列
      • 解释连接类型
        • 解释额外信息
        • 获取命名连接的执行计划信息
        • 估计查询性能
        • 笔者的验证 Demo
        相关产品与服务
        云数据库 SQL Server
        腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档