MySQL执行计划和索引实践

该EXPLAIN语句提供有关MySQL如何执行语句的信息。EXPLAIN作品有SELECT,DELETE,INSERT,REPLACE,和UPDATE语句。

Posted by Sunfy on 2021-11-29
Words 7.3k and Reading Time 28 Minutes
Viewed Times
Viewed Times
Visitors In Total

Explain工具

使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析查询语句或是结构的性能瓶颈

在 select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是执行SQL

注意:如果 from 中包含子查询,仍会执行该子查询,将结果放入临时表中

官方文档:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

EXPLAIN语句提供有关 MySQL 如何执行语句的信息。 EXPLAIN作品有 SELECTDELETEINSERTREPLACE,和 UPDATE语句。

EXPLAINSELECT语句中使用的每个表返回一行信息 。它按照 MySQL 在处理语句时读取它们的顺序列出输出中的表。MySQL 使用嵌套循环连接方法解析所有连接。这意味着 MySQL 从第一个表中读取一行,然后在第二个表中找到匹配的行,第三个表,依此类推。处理完所有表后,MySQL 输出选定的列并通过表列表回溯,直到找到具有更多匹配行的表。从此表中读取下一行,然后处理下一个表。

EXPLAIN输出包括分区信息。此外,对于SELECT 语句,EXPLAIN产生可与被显示扩展信息 SHOW WARNINGS之后的 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 没有任何 附加信息

JSONNULL格式的EXPLAIN 输出中未显示的JSON 属性。

  • id(JSON名: select_id

    SELECT标识符。这是SELECT查询中的序列号 。NULL如果该行引用其他行的联合结果,则该值可以是。在这种情况下,该 table列显示的值类似于 表示该行引用具有 和值的行的 并集 。 <union*M*,*N*>``idM**N

  • select_type (JSON 名称:无)

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

    | select_type 价值 | JSON 名称 | 意义 |
    | :—————————————————————————————- | :—————————————- | :—————————————————————————————- |
    | SIMPLE | 没有任何 | 简单SELECT(不使用 UNION或子查询) |
    | PRIMARY | 没有任何 | 最外面 SELECT |
    | UNION | 没有任何 | 中的第二个或以后的SELECT语句 UNION |
    | DEPENDENT UNION | dependent( true) | a 中的第二个或后面的SELECT语句 UNION,取决于外部查询 |
    | UNION RESULT | union_result | 的结果UNION。 |
    | SUBQUERY | 没有任何 | 首先SELECT在子查询 |
    | DEPENDENT SUBQUERY | dependent( true) | 首先SELECT在子查询中,依赖于外部查询 |
    | DERIVED | 没有任何 | 派生表 |
    | MATERIALIZED | materialized_from_subquery | 物化子查询 |
    | UNCACHEABLE SUBQUERY | cacheable( false) | 无法缓存结果并且必须为外部查询的每一行重新评估的子查询 |
    | UNCACHEABLE UNION | cacheable( false) | UNION 属于不可缓存子查询的第二个或以后的选择(请参阅 UNCACHEABLE SUBQUERY) |

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

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

    子查询的可缓存性不同于在查询缓存中缓存查询结果。子查询缓存发生在查询执行期间,而查询缓存仅用于在查询执行完成后存储结果。

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

    select_typeSELECT语句 的值显示受影响表的语句类型。例如,select_typeis DELETEfor DELETE语句。

  • table(JSON名: table_name

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

    • <union*M*,*N*>: 行是指具有 和id值的行 的 M并集 N
    • <derived*N*>:该行是指用于与该行的派生表结果id的值 N。例如,派生表可能来自FROM子句中的子查询 。
    • <subquery*N*>:该行是指与物化子查询该行的结果id 的值N。请参阅 第 8.2.2.2 节,“使用实现优化子查询”
  • partitions(JSON名: partitions

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

  • type(JSON名: access_type

    联接类型。有关不同类型的说明,请参阅 EXPLAIN 联接类型

  • possible_keys(JSON名: possible_keys

    possible_keys列指示 MySQL 可以选择从中查找该表中行的索引。请注意,此列完全独立于从 的输出中显示的表的顺序 EXPLAIN。这意味着某些键possible_keys在实际中可能无法与生成的表顺序一起使用。

    如果此列是NULL(或在 JSON 格式的输出中未定义),则没有相关索引。在这种情况下,您可以通过检查WHERE 子句来检查它是否引用了适合编制索引的某些列或多列,从而提高查询的性能。如果是这样,请创建适当的索引并EXPLAIN再次检查查询 。见 第 13.1.8 节,“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在您的查询。见第 8.9.4 节,“索引提示”

    对于MyISAM表,运行 ANALYZE TABLE有助于优化器选择更好的索引。对于 MyISAM表,myisamchk —analyze执行相同的操作。请参阅 第 13.7.2.1 节,“ANALYZE TABLE 语句”第 7.6 节,“MyISAM 表维护和崩溃恢复”

  • key_len(JSON名: key_length

    key_len列表示 MySQL 决定使用的键的长度。的值 key_len使您能够确定 MySQL 实际使用的多部分键的多少部分。如果key列说 NULLkey_len 列也说NULL

    由于密钥存储格式的原因,列的密钥长度NULL 比列的长度NOT 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 名称:无)

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

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

联接类型

typeEXPLAIN输出介绍如何联接表。在 JSON 格式的输出中,这些作为access_type属性的值被找到。下面的列表描述了连接类型,从最好的类型到最差的类型:

  • system

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

  • const

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

    const当您将 aPRIMARY KEYUNIQUE索引的所有部分与常量值进行比较时使用。在以下查询中,tbl_name可以用作const 表:

    1
    2
    3
    4
    SELECT * FROM tbl_name WHERE primary_key=1;

    SELECT * FROM tbl_name
    WHERE primary_key_part1=1 AND primary_key_part2=2;
  • eq_ref

    对于先前表中的每个行组合,从该表中读取一行。除了 systemand const类型之外,这是最好的连接类型。当连接使用索引的所有部分并且索引是一个 PRIMARY KEYUNIQUE NOT NULL索引时使用它。

    eq_ref可用于使用=运算符进行比较的索引列 。比较值可以是常量或表达式,该表达式使用在此表之前读取的表中的列。在以下示例中,MySQL 可以使用 eq_ref连接来处理 ref_table

    1
    2
    3
    4
    5
    6
    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如果联接仅使用键的最左前缀或键不是 aPRIMARY KEYUNIQUE索引(换句话说,如果联接无法根据键值选择单行),则使用。如果使用的键只匹配几行,这是一个很好的连接类型。

    ref可用于使用=or<=> 运算符进行比较的索引列 。在以下示例中,MySQL 可以使用 ref连接来处理 ref_table

    1
    2
    3
    4
    5
    6
    7
    8
    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

    1
    2
    SELECT * FROM ref_table
    WHERE key_column=expr OR key_column IS NULL;
  • index_merge
    此连接类型表示使用了索引合并优化。在这种情况下,key输出行中的列包含所使用索引的列表,并key_len包含所使用索引 的最长关键部分的列表。有关更多信息,请参阅 第 8.2.1.3 节,“索引合并优化”
  • unique_subquery

    这种类型替代 了以下形式的eq_ref一些 IN子查询:

    1
    value IN (SELECT primary_key FROM single_table WHERE some_expr)

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

  • index_subquery

    这种联接类型类似于 unique_subquery. 它取代了IN子查询,但它适用于以下形式的子查询中的非唯一索引:

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

    仅检索给定范围内的行,使用索引来选择行。的key 输出行中的列指示使用哪个索引。将key_len包含已使用的时间最长的关键部分。该ref列适用 NULL于这种类型。

    range当使用=, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, 或 IN()运算符中的任何一个将键列与常量进行比较时,可以使用 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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通过添加索引来避免 基于常量值或早期表中的列值从表中检索行。

额外信息

ExtraEXPLAIN输出包含MySQL解决查询的额外信息。以下列表说明了可以出现在此列中的值。每个项目还为 JSON 格式的输出指示哪个属性显示Extra值。对于其中一些,有一个特定的属性。其他显示为message 属性的文本。

如果您想尽可能快地进行查询,请注意and 的Extra列值,或者,在 JSON 格式的输出中, for 和 properties 等于 。 Using filesort``Using temporary``EXPLAIN``using_filesort``using_temporary_table``true

  • Child of '*table*' pushed join@1(JSON:message 文本)

    此表被引用为 table可以下推到 NDB 内核的连接中的子项。仅适用于 NDB Cluster,当启用下推连接时。有关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子句始终为 false,不能选择任何行。

  • Impossible WHERE(JSON属性: message

    WHERE子句始终为 false,不能选择任何行。

  • 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

    对于DELETEor UPDATE,优化器在分区修剪后没有发现要删除或更新的内容。它的含义类似于Impossible WHERE forSELECT语句。

  • No tables used(JSON属性: message

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

    对于INSERTor REPLACE语句, EXPLAIN当没有SELECT 部件时显示该值。例如,它出现是EXPLAIN INSERT INTO t VALUES(10)因为它等价于 EXPLAIN INSERT INTO t SELECT 10 FROM DUAL

  • Not exists(JSON属性: message

    MySQL 能够对LEFT JOIN 查询进行优化,并且在找到符合LEFT JOIN条件的行后,不会检查该表中前一行组合的更多行。以下是可以通过这种方式优化的查询类型的示例:

    1
    2
    SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id
    WHERE t2.id IS NULL;

    假设t2.id定义为 NOT NULL。在这种情况下,MySQL使用 的值 扫描 t1和查找行 。如果 MySQL 在 中找到匹配的行 ,它知道 永远不可能是 ,并且不会扫描具有相同值的其余行。换句话说,对于 中的每一行,MySQL 只需要在 中进行一次查找,而不管 中实际匹配了多少行。 t2``t1.id``t2``t2.id``NULL``t2``id``t1``t2``t2

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

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

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

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

    索引从 1 开始编号,其顺序SHOW INDEX与表中 的 相同。索引映射值 N是指示哪些索引是候选的位掩码值。例如,值0x19(binary 11001) 表示考虑索引 1、4 和 5。

  • Scanned *N* databases(JSON属性: message

    这表示服务器在处理INFORMATION_SCHEMA表查询时执行的目录扫描次数 ,如第 8.2.3 节“优化 INFORMATION_SCHEMA 查询”中所述。的值N可以是 0、1 或 all

  • Select tables optimized away(JSON属性:message

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

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

    考虑以下隐式分组查询:

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

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

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

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

    假设这(c1, c2)是一个覆盖索引。使用此索引,c1 <= 10必须扫描所有行以找到最小值 c2。相比之下,请考虑以下查询:

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

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

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

  • Skip_open_tableOpen_frm_onlyOpen_full_table(JSON属性: message

    这些值表示适用于INFORMATION_SCHEMA 表查询的文件打开优化,如 第 8.2.3 节“优化 INFORMATION_SCHEMA 查询”中所述

    • Skip_open_table: 表文件不需要打开。通过扫描数据库目录,该信息已在查询中可用。
    • Open_frm_only: 只.frm需要打开表的文件。
    • Open_full_table:未优化的信息查找。的.frm.MYD.MYI文件必须被打开。
  • Start temporaryEnd temporary(JSON属性: message

    这表示半连接重复清除策略的临时表使用。

  • unique row not found(JSON属性: message

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

  • Using filesort(JSON属性: using_filesort

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

  • Using index(JSON属性: using_index

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

    对于InnoDB具有用户定义的聚集索引的表,即使列中Using index不存在 该索引,也可以使用该索引Extra。如果typeis indexkeyis就是这种情况 PRIMARY

  • Using index condition(JSON属性: using_index_condition

    通过访问索引元组并首先测试它们以确定是否读取完整的表行来读取表。通过这种方式,索引信息用于推迟(“下推”)读取全表行,除非有必要。请参见 第 8.2.1.5 节,“索引条件下推优化”

  • Using index for group-by(JSON属性:using_index_for_group_by

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

  • Using join buffer (Block Nested Loop)Using join buffer (Batched Key Access) (JSON属性:using_join_buffer

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

    在 JSON 格式的输出中, 的值 using_join_buffer始终是Block Nested Loop或 之一 Batched Key Access

    有关这些算法的更多信息,请参阅 Block Nested-Loop Join AlgorithmBatched Key Access Joins

  • Using MRR(JSON属性: message

    使用多范围读取优化策略读取表。请参见第 8.2.1.10 节,“多范围读取优化”

  • Using sort_union(...)Using union(...)Using intersect(...)(JSON属性: message

    这些指示显示如何为index_merge连接类型合并索引扫描的特定算法 。请参阅第 8.2.1.3 节,“索引合并优化”

  • Using temporary(JSON属性: using_temporary_table

    为了解决查询,MySQL 需要创建一个临时表来保存结果。如果查询包含以不同方式列出列的GROUP BYORDER BY子句,则通常会发生这种情况。

  • Using where(JSON属性: attached_condition

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

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

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

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

  • Zero limit(JSON属性: message

    查询有一个LIMIT 0子句,不能选择任何行。

EXPLAIN 输出解释

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

以下示例显示了如何根据 提供的信息逐步优化多表连接 EXPLAIN

假设您有SELECT此处显示的 语句,并且您计划使用EXPLAIN以下命令检查它 :

1
2
3
4
5
6
7
8
9
10
11
12
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语句会生成以下信息:

1
2
3
4
5
6
7
8
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)

因为typeALL为每个表,这个输出表明MySQL正在生成的所有表的笛卡儿积; 也就是说,行的每个组合。这需要相当长的时间,因为必须检查每个表中行数的乘积。对于手头的情况,该乘积为 74 × 2135 × 74 × 3872 = 45,268,558,720 行。如果桌子更大,您只能想象需要多长时间。

这里的一个问题是,如果将列声明为相同的类型和大小,则 MySQL 可以更有效地使用列上的索引。在这种情况下,VARCHARCHAR被认为是相同的,如果它们被声明为相同的大小。 tt.ActualPC被声明为 CHAR(10)and et.EMPLOYID is CHAR(15),因此存在长度不匹配。

要修复列长度之间的这种差异,请使用 ALTER TABLEActualPC10 个字符延长 到 15 个字符:

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

现在tt.ActualPCet.EMPLOYID都是 VARCHAR(15)EXPLAIN再次执行该 语句会产生以下结果:

1
2
3
4
5
6
7
8
9
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比较的列长度不匹配:

1
2
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
MODIFY ClientID VARCHAR(15);

修改后, EXPLAIN生成此处显示的输出:

1
2
3
4
5
6
7
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 分析密钥分布:

1
mysql> ANALYZE TABLE tt;

使用额外的索引信息,连接是完美的并 EXPLAIN产生以下结果:

1
2
3
4
5
6
7
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

rows输出中 的列 EXPLAIN是来自 MySQL 连接优化器的有根据的猜测。通过将rows乘积与查询返回的实际行数进行比较,检查数字是否更接近真实 情况。如果数字完全不同,您可能会通过STRAIGHT_JOINSELECT语句中使用并尝试在FROM子句中以不同顺序列出表来 获得更好的性能 。(但是, STRAIGHT_JOIN可能会阻止使用索引,因为它禁用了半连接转换。请参阅第 8.2.2.1 节,“使用半连接转换 优化子查询、派生表和视图引用”.)

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


Copyright 2021 sunfy.top ALL Rights Reserved

...

...

00:00
00:00