explain

开发过程中经常使用explain,在执行语句前使用explain,可以列出这条语句的执行计划,执行计划是优化器经过规则的优化、成本的优化后生成的。例如使用什么访问方法、连接查询的顺序、用到的索引等等

explain 返回的字段:

+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+

这些字段对应的基本解释是:

列名 描述
id 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
select_type SELECT关键字对应的那个查询的类型
table 表名
type 针对单表的访问方法
possible_keys 可能用到的索引
key 实际上使用的索引
key_len 实际使用到的索引长度
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
Extra 一些额外的信息

在解释各个字段之前先创建两张结构一样的表,同时向两张表分别插入10000条数据:

CREATE TABLE s1 (
    id INT NOT NULL AUTO_INCREMENT,
    key1 VARCHAR(100),
    key2 INT,
    key3 VARCHAR(100),
    key_part1 VARCHAR(100),
    key_part2 VARCHAR(100),
    key_part3 VARCHAR(100),
    common_field VARCHAR(100),
    PRIMARY KEY (id),
    KEY idx_key1 (key1),
    UNIQUE KEY idx_key2 (key2),
    KEY idx_key3 (key3),
    KEY idx_key_part(key_part1, key_part2, key_part3)
) Engine=InnoDB CHARSET=utf8;

CREATE TABLE s2 (
    id INT NOT NULL AUTO_INCREMENT,
    key1 VARCHAR(100),
    key2 INT,
    key3 VARCHAR(100),
    key_part1 VARCHAR(100),
    key_part2 VARCHAR(100),
    key_part3 VARCHAR(100),
    common_field VARCHAR(100),
    PRIMARY KEY (id),
    KEY idx_key1 (key1),
    UNIQUE KEY idx_key2 (key2),
    KEY idx_key3 (key3),
    KEY idx_key_part(key_part1, key_part2, key_part3)
) Engine=InnoDB CHARSET=utf8;

id字段


查询语句中每出现一个SELECT关键字,MySQL就会为它分配一个唯一的id值

  • 连接查询

    对于连接查询来说,一个SELECT关键字后边的FROM子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的id值都是相同的,出现在前边的表表示驱动表,出现在后边的表表示被驱动表 例如:

      EXPLAIN SELECT * FROM s1 INNER JOIN s2;
    	
      +----+-------------+-------+
      | id | select_type | table |
      +----+-------------+-------+
      |  1 | SIMPLE      | s1    |
      |  1 | SIMPLE      | s2    |
      +----+-------------+-------+
    
  • 子查询

    每个SELECT关键字都会对应一个唯一的id值,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
    	
      +----+-------------+-------+
      | id | select_type | table |
      +----+-------------+-------+
      |  1 | PRIMARY     | s1    |
      |  2 | SUBQUERY    | s2    |
      +----+-------------+-------+
    

    这篇文章提到查询优化器可能对子查询重写,转换为连接查询,此时生成执行计划id值相同,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a');
    	
      +----+-------------+-------+
      | id | select_type | table |
      +----+-------------+-------+
      |  1 | SIMPLE      | s2    |
      |  1 | SIMPLE      | s1    |
      +----+-------------+-------+
    
  • UNION

    对于包含UNION子句的查询语句来说,每个SELECT关键字对应一个id值,例如:

      EXPLAIN SELECT * FROM s1  UNION SELECT * FROM s2;
    	
      +----+--------------+------------+
      | id | select_type  | table      |
      +----+--------------+------------+
      |  1 | PRIMARY      | s1         |
      |  2 | UNION        | s2         |
      | NULL | UNION RESULT | <union1,2> |
      +----+--------------+------------+
    

    UNION查询它会把多个查询的结果集合并起来并对结果集中的记录进行去重,通过内部的临时表去重。这个例子UNION子句是为了把id为1的查询和id为2的查询的结果集合并起来并去重, 所以在内部创建了一个名为<union1, 2>的临时表(就是执行计划第三条记录的table列的名称),id为NULL表明这个临时表是为了合并两个查询的结果集而创建的

    UNION ALL不需要为最终的结果集进行去重,它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户,所以也就不需要使用临时表。 所以在包含UNION ALL子句的查询的执行计划中,就没有那个id为NULL的记录。例如:

      EXPLAIN SELECT * FROM s1  UNION ALL SELECT * FROM s2;
    	
      +----+-------------+-------+
      | id | select_type | table |
      +----+-------------+-------+
      |  1 | PRIMARY     | s1    |
      |  2 | UNION       | s2    |
      +----+-------------+-------+
    

select_type字段


每个select查询都会对应一个select类型,下面列出了一些常用的类型

  • SIMPLE

    查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型,连接查询也算是SIMPLE类型

  • PRIMARY

    对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type值就是PRIMARY,例如:

      EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
      +----+--------------+------------+
      | id | select_type  | table      |
      +----+--------------+------------+
      |  1 | PRIMARY      | s1         |
      |  2 | UNION        | s2         |
      | NULL | UNION RESULT | <union1,2> |
      +----+--------------+------------+
    
  • UNION

    对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type值就是UNION,参照PRIMARY例子

  • UNION RESULT

    使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT,参照PRIMARY例子

  • SUBQUERY

    如果包含子查询的查询语句不能够转为对应的semi-join的形式,关于semi-join参考这篇文章,并且该子查询是不相关子查询, 并且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个SELECT关键字代表的那个查询的select_type就是SUBQUERY,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
      +----+-------------+-------+
      | id | select_type | table |
      +----+-------------+-------+
      |  1 | PRIMARY     | s1    |
      |  2 | SUBQUERY    | s2    |
      +----+-------------+-------+
    

    select_type为SUBQUERY的子查询由于会被物化,所以只需要执行一遍

  • DEPENDENT SUBQUERY

    如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是相关子查询,则该子查询的第一个SELECT关键字代表的那个查询的select_type就是DEPENDENT SUBQUERY,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
      +----+--------------------+-------+
      | id | select_type        | table |
      +----+--------------------+-------+
      |  1 | PRIMARY            | s1    |
      |  2 | DEPENDENT SUBQUERY | s2    |
      +----+--------------------+-------+
    

    select_type为DEPENDENT SUBQUERY的查询可能会被执行多次

  • DEPENDENT UNION

    在包含UNION或者UNION ALL的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的select_type的值就是DEPENDENT UNION。例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
    	
      +----+--------------------+------------+
      | id | select_type        | table      |
      +----+--------------------+------------+
      |  1 | PRIMARY            | s1         |
      |  2 | DEPENDENT SUBQUERY | s2         |
      |  3 | DEPENDENT UNION    | s1         |
      | NULL | UNION RESULT       | <union2,3> |
      +----+--------------------+------------+
    

    SELECT key1 FROM s1 WHERE key1 = 'b'这个查询的select_type就是DEPENDENT UNION

  • DERIVED

    对于采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的select_type就是DERIVED,例如:

      EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS derived_s1 where c > 1;
    	
      +----+-------------+------------+
      | id | select_type | table      |
      +----+-------------+------------+
      |  1 | PRIMARY     | <derived2> |
      |  2 | DERIVED     | s1         |
      +----+-------------+------------+
    

    id为1的记录代表外层查询,table列显示的是,表示该查询是针对将派生表物化之后的表进行查询的

  • MATERIALIZED

    当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type属性就是MATERIALIZED,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
      +----+--------------+-------------+
      | id | select_type  | table       |
      +----+--------------+-------------+
      |  1 | SIMPLE       | s1          |
      |  1 | SIMPLE       | <subquery2> |
      |  2 | MATERIALIZED | s2          |
      +----+--------------+-------------+
    

    执行计划id值为2,说明该条记录对应的是一个单表查询,从它的select_type值为MATERIALIZED可以看出,查询优化器是要把子查询先转换成物化表。 执行计划id值为1的记录,说明这两条记录对应的表进行连接查询,第2条记录的table列的值是,说明该表其实就是id为2对应的子查询执行之后产生的物化表, 然后将s1和该物化表进行连接查询

type字段


type表示对表执行查询使用的访问方法,关于访问方法可以参考这篇文章,下面列举一些INNODB常用的访问方法,其中除了All这个访问方法外,其余的访问方法都能用到索引, 除了index_merge访问方法外,其余的访问方法都最多只能用到一个索引,列出的顺序表明查询性能逐渐变差

  • const

    主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const,例如:

      EXPLAIN SELECT * FROM s1 WHERE id = 5;
      +----+-------------+-------+-------+
      | id | select_type | table | type  |
      +----+-------------+-------+-------+
      |  1 | SIMPLE      | s1    | const |
      +----+-------------+-------+-------+
    
  • eq_ref

    在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的, 如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较,则对该被驱动表的访问方法就是eq_ref,例如:

      EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
    	
      +----+-------------+-------+--------+
      | id | select_type | table | type   |
      +----+-------------+-------+--------+
      |  1 | SIMPLE      | s1    | ALL    |
      |  1 | SIMPLE      | s2    | eq_ref |
      +----+-------------+-------+--------+
    
  • ref

    当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref

  • ref_or_null

    当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null

      EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
      +----+-------------+-------+-------------+
      | id | select_type | table | type        |
      +----+-------------+-------+-------------+
      |  1 | SIMPLE      | s1    | ref_or_null |
      +----+-------------+-------+-------------+
    
  • index_merge

    例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
      +----+-------------+-------+-------------+
      | id | select_type | table | type        |
      +----+-------------+-------+-------------+
      |  1 | SIMPLE      | s1    | index_merge |
      +----+-------------+-------+-------------+
    
  • unique_subquery

    类似于两表连接中被驱动表的eq_ref访问方法,unique_subquery是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS子查询, 而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery,例如:

      EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
    	
      +----+--------------------+-------+-----------------+
      | id | select_type        | table | type            |
      +----+--------------------+-------+-----------------+
      |  1 | PRIMARY            | s1    | ALL             |
      |  2 | DEPENDENT SUBQUERY | s2    | unique_subquery |
      +----+--------------------+-------+-----------------+
    
  • index_subquery

    index_subquery与unique_subquery类似,只不过访问子查询中的表时使用的是普通的索引,例如:

      EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key3 FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
      +----+--------------------+-------+----------------+
      | id | select_type        | table | type           |
      +----+--------------------+-------+----------------+
      |  1 | PRIMARY            | s1    | ALL            |
      |  2 | DEPENDENT SUBQUERY | s2    | index_subquery |
      +----+--------------------+-------+----------------+
    
  • range

    如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
      +----+-------------+-------+-------+
      | id | select_type | table | type  |
      +----+-------------+-------+-------+
      |  1 | SIMPLE      | s1    | range |
      +----+-------------+-------+-------+
    
  • index

    当可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index,例如:

      EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
      +----+-------------+-------+-------+
      | id | select_type | table | type  |
      +----+-------------+-------+-------+
      |  1 | SIMPLE      | s1    | index |
      +----+-------------+-------+-------+
    

    上述查询中的搜索列表中只有key_part2一个列,而且搜索条件中也只有key_part3一个列,这两个列又恰好包含在idx_key_part这个索引中, 可是搜索条件key_part3不能直接使用该索引进行ref或者range方式的访问,只能扫描整个idx_key_part索引的记录,所以查询计划的type列的值就是index

  • all

    全表扫描

possible_keys和key字段


possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引,possible_keys列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间

key列表示实际用到的索引,表示经过查询优化器计算使用不同索引的成本后,最后决定使用的索引

例如:

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
+----+-------------+-------+------+-------------------+----------+
| id | select_type | table | type | possible_keys     | key      |
+----+-------------+-------+------+-------------------+----------+
|  1 | SIMPLE      | s1    | ref  | idx_key1,idx_key3 | idx_key3 |
+----+-------------+-------+------+-------------------+----------+

key_len


key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,key_len列主要是为了让我们区分某个使用联合索引的查询具体用了几个索引列,它是由这三个部分构成的:

  • 对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。

  • 如果该索引列可以存储NULL值,则key_len比不可以存储NULL值时多1个字节。

  • 对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度

不为NULL:

EXPLAIN SELECT * FROM s1 WHERE id = 5;
+----+-------------+-------+-------+---------------+---------+---------+
| id | select_type | table | type  | possible_keys | key     | key_len |
+----+-------------+-------+-------+---------------+---------+---------+
|  1 | SIMPLE      | s1    | const | PRIMARY       | PRIMARY | 4       |
+----+-------------+-------+-------+---------------+---------+---------+

可以为NULL:

EXPLAIN SELECT * FROM s1 WHERE key2 = 5;
+----+-------------+-------+-------+---------------+----------+---------+
| id | select_type | table | type  | possible_keys | key      | key_len |
+----+-------------+-------+-------+---------------+----------+---------+
|  1 | SIMPLE      | s1    | const | idx_key2      | idx_key2 | 5       |
+----+-------------+-------+-------+---------------+----------+---------+

变长可以为NULL:

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
+----+-------------+-------+------+---------------+----------+---------+
| id | select_type | table | type | possible_keys | key      | key_len |
+----+-------------+-------+------+---------------+----------+---------+
|  1 | SIMPLE      | s1    | ref  | idx_key1      | idx_key1 | 303     |
+----+-------------+-------+------+---------------+----------+---------+

EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';
+----+-------------+-------+------+---------------+--------------+---------+
| id | select_type | table | type | possible_keys | key          | key_len |
+----+-------------+-------+------+---------------+--------------+---------+
|  1 | SIMPLE      | s1    | ref  | idx_key_part  | idx_key_part | 606     |
+----+-------------+-------+------+---------------+--------------+---------+

ref字段


当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery其中之一时, ref列展示的就是与索引列作等值匹配的是一个常数还是某个列,例如:

与索引列进行等值匹配的对象是常数:

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
+----+-------------+-------+------+---------------+----------+---------+-------+
| id | select_type | table | type | possible_keys | key      | key_len | ref   |
+----+-------------+-------+------+---------------+----------+---------+-------+
|  1 | SIMPLE      | s1    | ref  | idx_key1      | idx_key1 | 303     | const |
+----+-------------+-------+------+---------------+----------+---------+-------+

与索引列进行等值匹配的对象是一列:

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
+----+-------------+-------+--------+---------------+---------+---------+-----------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref             |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+
|  1 | SIMPLE      | s1    | ALL    | PRIMARY       | NULL    | NULL    | NULL            |
|  1 | SIMPLE      | s2    | eq_ref | PRIMARY       | PRIMARY | 4       | test.s1.id 	  |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+

与索引列进行等值匹配的对象是一个函数:

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);
+----+-------------+-------+------+---------------+----------+---------+------+
| id | select_type | table | type | possible_keys | key      | key_len | ref  |
+----+-------------+-------+------+---------------+----------+---------+------+
|  1 | SIMPLE      | s1    | ALL  | NULL          | NULL     | NULL    | NULL |
|  1 | SIMPLE      | s2    | ref  | idx_key1      | idx_key1 | 303     | func |
+----+-------------+-------+------+---------------+----------+---------+------+

rows字段


如果优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows列就代表预计需要扫描的行数

如果使用索引来执行查询时,执行计划的rows列就代表预计扫描的索引记录行数。例如:

EXPLAIN SELECT * FROM s1 WHERE key1 > 's';
+----+-------------+-------+-------+---------------+----------+---------+------+------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows |
+----+-------------+-------+-------+---------------+----------+---------+------+------+
|  1 | SIMPLE      | s1    | range | idx_key1      | idx_key1 | 303     | NULL |  921 |
+----+-------------+-------+-------+---------------+----------+---------+------+------+

执行计划的rows列的值是921,查询优化器在经过分析使用idx_key1进行查询的成本之后,觉得满足key1 > ‘s’这个条件的记录只有921条

Extra字段


列举一些常见的Extra属性

  • No tables used

    当查询语句的没有FROM子句时将会提示该额外信息,例如:

      EXPLAIN SELECT 1;
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
      |  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
    
  • Impossible WHERE

    查询语句的WHERE子句永远为FALSE时将会提示该额外信息,例如:

      EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
      +----+-------------+-------+------+---------------+------+---------+------+------+------------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra            |
      +----+-------------+-------+------+---------------+------+---------+------+------+------------------+
      |  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE |
      +----+-------------+-------+------+---------------+------+---------+------+------+------------------+
    
  • No matching min/max row

    当查询列表处有MIN或者MAX聚集函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息,例如:

      EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                   |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------------------+
      |  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No matching min/max row |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------------------+
    
  • Using index

    当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下,并不需要回表,在Extra列将会提示该额外信息,例如:

      EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a';
      +----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
      | id | select_type | table | type | possible_keys | key      | key_len | ref   | rows | Extra                    |
      +----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
      |  1 | SIMPLE      | s1    | ref  | idx_key1      | idx_key1 | 303     | const |   15 | Using where; Using index |
      +----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
    
  • Using index condition

    如果在查询语句的执行过程中将要使用索引条件下推这个特性,在Extra列中将会显示Using index condition,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b';
      +----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------+
      | id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra                 |
      +----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------+
      |  1 | SIMPLE      | s1    | range | idx_key1      | idx_key1 | 303     | NULL |  184 | Using index condition |
      +----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------+
    

    索引条件下推代表着一个查询步骤,例如刚刚这个查询,它的步骤是:

    1. 先根据key1 > ‘z’这个条件,定位到二级索引idx_key1中对应的二级索引记录

    2. 对于指定的二级索引记录,先检测一下该记录是否满足key1 LIKE ‘%a’这个条件,如果这个条件不满足,则该二级索引记录就没必要回表

    3. 对于满足key1 LIKE ‘%a’这个条件的二级索引记录执行回表操作

    这个过程就是索引条件下推

  • Using where

    当使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息,例如:

      EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
      |  1 | SIMPLE      | s1    | ALL  | NULL          | NULL | NULL    | NULL |  7532| Using where |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
    

    当使用索引访问来执行对某个表的查询,并且该语句的WHERE子句中有除了该索引包含的列之外的其他搜索条件时,在Extra列中也会提示上述额外信息,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
      +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+
      | id | select_type | table | type | possible_keys | key      | key_len | ref   | rows | Extra       |
      +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+
      |  1 | SIMPLE      | s1    | ref  | idx_key1      | idx_key1 | 303     | const |   54 | Using where |
      +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+
    
  • Using join buffer (Block Nested Loop)

    在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法, 关于基于块的嵌套循环算法可以参考这篇文章,例如:

      EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                              |
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
      |  1 | SIMPLE      | s1    | ALL  | NULL          | NULL | NULL    | NULL | 7856 | NULL                                               |
      |  1 | SIMPLE      | s2    | ALL  | NULL          | NULL | NULL    | NULL |11254 | Using where; Using join buffer (Block Nested Loop) |
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    
  • Not exists

    当我们使用外连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且这个列又是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示Not exists额外信息,例如:

      EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
      +----+-------------+-------+------+---------------+----------+---------+--------------+------+-------------------------+
      | id | select_type | table | type | possible_keys | key      | key_len | ref          | rows | Extra                   |
      +----+-------------+-------+------+---------------+----------+---------+--------------+------+-------------------------+
      |  1 | SIMPLE      | s1    | ALL  | NULL          | NULL     | NULL    | NULL         | 8214 | NULL                    |
      |  1 | SIMPLE      | s2    | ref  | idx_key1      | idx_key1 | 303     | test.s1.key1 |    1 | Using where; Not exists |
      +----+-------------+-------+------+---------------+----------+---------+--------------+------+-------------------------+
    

    这个查询中s1表是驱动表,s2表是被驱动表,s2.id列是不允许存储NULL值的,而WHERE子句中又包含s2.id IS NULL的搜索条件, 这意味着必定是驱动表的记录在被驱动表中找不到匹配ON子句条件的记录才会把该驱动表的记录加入到最终的结果集,所以对于某条驱动表中的记录来说, 如果能在被驱动表中找到1条符合ON子句条件的记录,那么该驱动表的记录就不会被加入到最终的结果集,也就是说没有必要到被驱动表中找到全部符合ON子句条件的记录

  • Using intersect(…)、Using union(…)、Using sort_union(…)

    Extra列出现了Using intersect(…)提示,说明准备使用Intersect索引合并的方式执行查询,括号中的…表示需要进行索引合并的索引名称

    Extra列出现了Using union(…)提示,说明准备使用Union索引合并的方式执行查询

    Extra列出现了Using sort_union(…)提示,说明准备使用Sort-Union索引合并的方式执行查询

    例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND key3 = 'a';
      +----+-------------+-------+-------------+-------------------+-------------------+---------+------+------+-------------------------------------------------+
      | id | select_type | table | type        | possible_keys     | key               | key_len | ref  | rows | Extra                                           |
      +----+-------------+-------+-------------+-------------------+-------------------+---------+------+------+-------------------------------------------------+
      |  1 | SIMPLE      | s1    | index_merge | idx_key1,idx_key3 | idx_key3,idx_key1 | 303,303 | NULL |    1 | Using intersect(idx_key3,idx_key1); Using where |
      +----+-------------+-------+-------------+-------------------+-------------------+---------+------+------+-------------------------------------------------+
    
  • Zero Limit

    LIMIT子句的参数为0时,表示从表中不读出任何记录,将会提示该额外信息,例如:

       EXPLAIN SELECT * FROM s1 LIMIT 0;
       +----+-------------+-------+------+---------------+------+---------+------+------+------------+
       | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
       +----+-------------+-------+------+---------------+------+---------+------+------+------------+
       |  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Zero limit |
       +----+-------------+-------+------+---------------+------+---------+------+------+------------+
    
  • Using Filesort

    有些情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序filesort。 如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示Using filesort提示,例如:

      EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
      |  1 | SIMPLE      | s1    | ALL  | NULL          | NULL | NULL    | NULL | 8432 | Using filesort |
      +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
    

    如果查询中需要使用filesort的方式进行排序的记录非常多,那么这个过程是很耗费性能的,最好将使用文件排序的执行方式改为使用索引进行排序

  • Using Temporary

    在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,例如去重、排序之类的,在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中, 如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra列将会显示Using temporary提示,例如:

      EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;
      +----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                           |
      +----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
      |  1 | SIMPLE      | s1    | ALL  | NULL          | NULL | NULL    | NULL | 8432 | Using temporary; Using filesort |
      +----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
    

    Extra列不仅仅包含Using temporary提示,还包含Using filesort提示,MySQL会在包含GROUP BY子句的查询中默认添加上ORDER BY子句, 如果并不想为包含GROUP BY子句的查询进行排序,需要显式的写上ORDER BY NUL,例如:

      mysql> EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field ORDER BY NULL;
      +----+-------------+-------+------+---------------+------+---------+------+------+-----------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra           |
      +----+-------------+-------+------+---------------+------+---------+------+------+-----------------+
      |  1 | SIMPLE      | s1    | ALL  | NULL          | NULL | NULL    | NULL | 8432 | Using temporary |
      +----+-------------+-------+------+---------------+------+---------+------+------+-----------------+
    

    借助了临时表就需要维护成本,所以尽量使用索引代替临时表

  • Start temporary, End temporary

    在子查询这篇文章提到了semi-join,查询优化器会优先尝试将IN子查询转换成semi-join,而semi-join又有好多种执行策略,当执行策略为DuplicateWeedout时, 也就是通过建立临时表来实现为外层查询中的记录进行去重操作时,驱动表查询执行计划的Extra列将显示Start temporary提示,被驱动表查询执行计划的Extra列将显示End temporary提示 ,例如:

      EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a');
      +----+-------------+-------+------+---------------+----------+---------+-------------------+------+------------------------------+
      | id | select_type | table | type | possible_keys | key      | key_len | ref               | rows | Extra                        |
      +----+-------------+-------+------+---------------+----------+---------+-------------------+------+------------------------------+
      |  1 | SIMPLE      | s2    | ALL  | idx_key3      | NULL     | NULL    | NULL              | 7984 | Using where; Start temporary |
      |  1 | SIMPLE      | s1    | ref  | idx_key1      | idx_key1 | 303     | test.s2.key3 	   |    1 | End temporary                |
      +----+-------------+-------+------+---------------+----------+---------+-------------------+------+------------------------------+
    
  • LooseScan

    在将In子查询转为semi-join时,如果采用的是LooseScan执行策略,则在驱动表执行计划的Extra列就是显示LooseScan提示,例如:

      EXPLAIN SELECT * FROM s1 WHERE key3 IN (SELECT key1 FROM s2 WHERE key1 > 'z');
      +----+-------------+-------+-------+---------------+----------+---------+--------------+------+-------------------------------------+
      | id | select_type | table | type  | possible_keys | key      | key_len | ref          | rows | Extra                               |
      +----+-------------+-------+-------+---------------+----------+---------+--------------+------+-------------------------------------+
      |  1 | SIMPLE      | s2    | index | idx_key1      | idx_key1 | 303     | NULL         |  184 | Using where; Using index; LooseScan |
      |  1 | SIMPLE      | s1    | ref   | idx_key3      | idx_key3 | 303     | test.s2.key1 |    1 | NULL                                |
      +----+-------------+-------+-------+---------------+----------+---------+--------------+------+-------------------------------------+
    
  • FirstMatch(table_name)

    在将In子查询转为semi-join时,如果采用的是FirstMatch执行策略,则在被驱动表执行计划的Extra列就是显示FirstMatch提示,例如:

      EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key1 FROM s2 where s1.key3 = s2.key3);
      +----+-------------+-------+------+-------------------+----------+---------+----------------------+------+-----------------------------+
      | id | select_type | table | type | possible_keys     | key      | key_len | ref                  | rows | Extra                       |
      +----+-------------+-------+------+-------------------+----------+---------+----------------------+------+-----------------------------+
      |  1 | SIMPLE      | s1    | ALL  | idx_key3          | NULL     | NULL    | NULL                 | 9412 | Using where                 |
      |  1 | SIMPLE      | s2    | ref  | idx_key1,idx_key3 | idx_key1 | 303     | test.s1.common_field |    1 | Using where; FirstMatch(s1) |
      +----+-------------+-------+------+-------------------+----------+---------+----------------------+------+-----------------------------+