【MySQL】explain 执行计划输出格式说明(详细版)

文章目录

  • 1 Explain 的输出格式
    • 1.1 EXPLAIN Output Columns
    • 1.2 EXPLAIN Join Types
    • 1.3 Extra
    • 1.4 一个优化案例
  • 2 参考资料

1 Explain 的输出格式

EXPLAIN语句提供了有关MySQL如何执行语句的信息。EXPLAIN适用于SELECT、DELETE、INSERT、REPLACE和UPDATE语句。

EXPLAIN 解释了MySQL的模式如下:

  • EXPLAIN为SELECT语句中使用的每个表返回一行信息

  • 它按照MySQL读取表的顺序列出表(意味着explain的输出顺序就是表的读取顺序)

    也就是说:第一行就是驱动表

  • MySQL从第一个表中读取一行,然后在第二个表中找到一个匹配的行,然后再在第三个表中查找,依此类推,当所有表都被处理时,MySQL输出所选的列。并在表列表中回溯,直到找到一个有更多匹配行的表。从该表中读取下一行,并继续处理下一个表。

    也就是说,一般采用嵌套查的方式处理多个表

1.1 EXPLAIN Output Columns

本节介绍EXPLAIN生成的输出列。而针对重点列typeExtra 在后面的部分提供其他信息。

EXPLAIN的每一个输出行都提供了有关一个表的信息,每行都包括下表列出的值,第一列提供的是列名称

Column Meaning
id The SELECT identifier
select_type The SELECT type
table The table for the output row
partitions The matching partitions
type The join type
possible_keys The possible indexes to choose
key The index actually chosen
key_len The length of the chosen key
ref The columns compared to the index
rows Estimate of rows to be examined
filtered Percentage of rows filtered by table condition
Extra Additional information
  • id

    这是 SELECT 语句标识符,一个select语句有一个,注意是一个select语句一个id而不是一个表一个id,可能出现的情况是2个表都是同一个id。

    这个值可能是NULL,比如当M表和N表union的时候,这个时候table_name列显示为

  • select_type

    select 语句的类型。可选的值如下表:

    select_type Value Meaning
    SIMPLE Simple SELECT (not using UNION or subqueries)(只要不是union和子查询)
    PRIMARY Outermost SELECT(最外面的查询)
    UNION Second or later SELECT statement in a UNION(union)
    DEPENDENT UNION Second or later SELECT statement in a UNION, dependent on outer query(union且依赖外部查询)
    UNION RESULT Result of a UNION.( union之后的结果)
    SUBQUERY First SELECT in subquery(子查询)
    DEPENDENT SUBQUERY First SELECT in subquery, dependent on outer query(子查询且依赖外部查询)
    DERIVED Derived table(派生表)
    DEPENDENT DERIVED Derived table dependent on another table(派生表且依赖其他表)
    MATERIALIZED Materialized subquery(物化子查询)
    UNCACHEABLE SUBQUERY A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query
    UNCACHEABLE UNION The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY)

    DEPENDENT 语义有一个经典的就是『相关子查询』,

    派生表,是用于存储子查询产生的结果的临时表,这个子查询特指 FROM 子句 里的子查询

    物化表,也是用于存储子查询产生的结果的临时表,这个子查询特指 WHERE 子句中查询条件里的子查询。

  • table

    输出行所引用表的名称,可选的值如下:

    • M,N>:如M表和N表union之后的结果的
    • N>:
    • N>:
  • partitions(分区,略)

  • type(重点)

  • possible_keys

    可能使用的索引。如果是NULL则表示不存在索引此时需要检查一下表是否真的不需要索引

  • key

    key列表示MySQL实际决定使用的键(索引)

    1、可能存在的key不来自于possible_keys。比如查询的一些列来自索引1另外一些列来自索引2,也就是说此时发生了 覆盖索引,此时发生的情况就是key不来自possible_keys。

    2、如果为NULL则表示MySQL找不到要使用的索引

  • key_len

    key_len列表示MySQL决定使用的密钥的长度。key_len的值使您能够确定MySQL实际使用多部分密钥的多少部分。

    在对 varchar 类型列建立索引时,是必须要指定长度的完全没有必要对整个varchar做索引

  • ref

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

  • rows

    rows列表示MySQL认为执行查询必须检查的行数。

    是MySQL预测的检索的行数,而不是结果的行数

  • filtered

    已筛选列表示按表条件筛选的表行的估计百分比。最大值为100,这意味着没有对行进行筛选。rows * filtered就是剩下的参与join的行数

  • Extra(重点)

    本列包含有关MySQL如何解析查询的其他信息

1.2 EXPLAIN Join Types

针对上文提到的type类型,这里重点说明。

EXPLAIN输出的type列描述了如何联接表。以下列表描述了连接类型,按从最佳类型到最差类型的顺序排列:

  • system

    只有一行数据的表,是const的一种特殊情况

    CREATE TABLE t(i int) Engine=MyISAM;
    INSERT INTO t VALUES(1);
    EXPLAIN SELECT * FROM t;
    
  • const

    常量级别,表中最多只匹配一行且在查询开始的时候就被读取到了。这种情况就是 PRIMARY KEYUNIQUE。举例:

    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 KYEUNIQUE NOT NULL 会使用。举例如下:

    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

    引用,跟 eq_ref 不同的是ref可能会匹配多行而eq_ref匹配一行。对于前一个表中的每一个行组合,都会从此表中读取具有匹配索引值的所有行。当键不是PRIMARY key或UNIQUE索引(换句话说,如果联接不能根据键值选择一行),则使用ref。举例:

    -- 不是 primary key 或者 unique
    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

  • ref_or_null

    这个join type类型跟ref类似,但是可能会包括null。举例:

    SELECT * FROM ref_table
      WHERE key_column=expr OR key_column IS NULL;
    
  • index_merge

    此联接类型表示使用了索引合并优化。在查询的列都来自索引时可能会发生。

  • unique_subquery

    用来在子查询中代替 eq_ref。举例:

    -- primary_key 是唯一索引
    value IN (SELECT primary_key FROM single_table WHERE some_expr)
    
  • index_subquery

    跟 unique_subquery类似。它代替 IN 子查询,但是它和非唯一索引一起工作。举例:

    -- key_column 是非唯一索引
    value IN (SELECT key_column FROM single_table WHERE some_expr)
    
  • range

    范围。使用索引选择给定范围的行的类型就是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差不多效率除了特殊情况外。

  • all

    全表扫描

1.3 Extra

EXPLAIN输出的Extra列包含有关MySQL如何解析查询的附加信息。以下列表说明了可以在此列中显示的值。以下列举几个常见的

  • Backward index scan

    反向索引扫描

  • const row not found

  • Distinct

  • Full scan on NULL key

  • Impossible HAVING

    不可能的having条件

  • Impossible WHERE

    不可能的where条件

  • No tables used

  • unique row not found

  • Using filesort

  • Using index

    只使用索引树中的信息从表中检索列信息,而不必进行额外的查找来读取实际行。当查询仅使用作为单个索引一部分的列时,可以使用此策略。

  • Using temporary

    为了解决查询,MySQL需要创建一个临时表来保存结果。通常发生在GROUP BY和ORDER BY子句

  • Using where

1.4 一个优化案例

有如下的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;

被比较的列如下:

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 Column Data Type
tt ActualPC CHAR(10)
tt AssignedPC CHAR(10)
tt ClientID CHAR(10)
et EMPLOYID CHAR(15)
do CUSTNMBR CHAR(15)

现在用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)

现在应该如何优化?

分析:

1、从执行计划的输出可以看到所有的链接类型都是ALL,这是全表扫描非常地效;从rows列的乘积 74 * 2135 * 74 * 3872 的结果可以看出需要扫描的行的数量将非常多(即使结果只有很少一部分);但是可以看到对表却是建立了索引,那为啥索引没有被使用到?

2、仔细观察发现是因为字段的类型长度不一样,有 char(10) 和 char(15),很明显只能扩长度不能缩小长度。执行如下操作重新观察执行计划

ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),MODIFY ClientID   VARCHAR(15);
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

达到这一步基本已经很完美了,索引基本上都使用到了,而且是eq_ref和ref效率都还可以。

3、但是仔细分析发现

  • 在 Extra 列使用过滤条件的列(即第二列)并没有作为驱动表,驱动表是et表(第一行是驱动表)。

  • 优化器预估tt扫描52行,et扫描74行,既然tt表扫描的行少,那应该让tt表作为驱动表

4、执行以下语句让MySQL分析关键字的分布情况(在Oracle中也叫做收集统计信息)

ANALYZE TABLE tt;

5、重新查看执行计划,如下:

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

2 参考资料

官网:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

书籍:《InnoDB 存储引擎》,该书电子版书籍作者无套路免费下载


传送门: 保姆式Spring5源码解析

欢迎与作者一起交流技术和工作生活

联系作者

你可能感兴趣的:(mysql,mysql,数据库)