Explain 详解

素颜马尾好姑娘i 2022-10-08 13:23 303阅读 0赞

0、基础

  • explain语句输出的各个列作用如下

























































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

1、执行计划输出中各列详解

1.1 table

  • EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名

    mysql> explain select * from single_table s1 inner join single_table s2;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———————————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———————————————————-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using join buffer (Block Nested Loop) |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———————————————————-+

1.2 id

  • 对于连接查询来说,一个SELECT关键字后边的FROM子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录, 但是这些记录的id值都是相同的(归属于同个select的多个表,id一致)
  • 在连接查询的执行计划中,每个表都会对应一条记录,这些记录的id列的值是相同的,出现在前边的表表示驱动表,出现在后边的表表示被驱动表
  • 对于包含子查询的语句,每个select关键字都对应不同的id值

    explain
    select * from single_table s1 where s1.key3=’a’ or s1.key1 in

    1. (select s2.key1 from single_table s2);

    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 2 | SUBQUERY | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 1000 | 100.00 | Using index |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+

1.3 select_type

  • select_type取值

(1)SIMPLE

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

    mysql> explain select * from single_table s1;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———-+

    mysql> explain select * from single_table s1 inner join single_table s2;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———————————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———————————————————-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using join buffer (Block Nested Loop) |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+———————————————————-+

(2)PRIMARY

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

    mysql> EXPLAIN SELECT FROM single_table s1 UNION SELECT FROM single_table s2;
    +——+———————+——————+——————+———+———————-+———+————-+———+———+—————+————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+———————+——————+——————+———+———————-+———+————-+———+———+—————+————————-+
    | 1 | PRIMARY | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 2 | UNION | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | NULL | UNION RESULT | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
    +——+———————+——————+——————+———+———————-+———+————-+———+———+—————+————————-+

(3)UNION

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

(4)UNION RESULT

  • MySQL选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT(见上一个例子)

(5)SUBQUERY

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key3 = ‘a’ or key1 IN (SELECT key1 FROM single_table s2);
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 2 | SUBQUERY | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 1000 | 100.00 | Using index |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+

(6)DEPENDENT SUBQUERY

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key3 = ‘a’ or key1 IN (SELECT key1 FROM single_table s2 WHERE s1.key2 = s2.key2);
    +——+——————————+———-+——————+———+—————————-+—————+————-+————————-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————————+———-+——————+———+—————————-+—————+————-+————————-+———+—————+——————-+
    | 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 2 | DEPENDENT SUBQUERY | s2 | NULL | ref | idx_key2,idx_key1 | idx_key2 | 5 | test123.s1.key2 | 1 | 10.00 | Using where |
    +——+——————————+———-+——————+———+—————————-+—————+————-+————————-+———+—————+——————-+

(7)DEPENDENT UNION

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 IN (SELECT key1 FROM single_table s2 WHERE key1 = ‘a’ UNION SELECT key1 FROM single_table s1 WHERE key1 = ‘b’);
    +——+——————————+——————+——————+———+———————-+—————+————-+———-+———+—————+—————————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————————+——————+——————+———+———————-+—————+————-+———-+———+—————+—————————————+
    | 1 | PRIMARY | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 2 | DEPENDENT SUBQUERY | s2 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 1 | 100.00 | Using where; Using index |
    | 3 | DEPENDENT UNION | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 1 | 100.00 | Using where; Using index |
    | NULL | UNION RESULT | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
    +——+——————————+——————+——————+———+———————-+—————+————-+———-+———+—————+—————————————+

(8)DERIVED

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

    mysql> EXPLAIN SELECT FROM (SELECT key1,count() as c FROM single_table s1 GROUP BY key1) AS derived_s1 where c > 1;
    +——+——————-+——————+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+——————+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33 | Using where |
    | 2 | DERIVED | s1 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 1000 | 100.00 | Using index |
    +——+——————-+——————+——————+———-+———————-+—————+————-+———+———+—————+——————-+

  1. id2的记录就代表子查询的执行方式,它的select_typeDERIVED,说明该子查询是以物化的方式执行的。
  2. id1的记录代表外层查询,它的table列显示的是<derived2>, 表示该查询是针对将派生表物化之后的表进行查询的

(9)MATERIALIZED

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 IN (SELECT key1 FROM single_table s2);
    +——+———————+——————-+——————+————+———————-+——————+————-+————————-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+———————+——————-+——————+————+———————-+——————+————-+————————-+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 1 | SIMPLE | | NULL | eq_ref | | | 303 | test123.s1.key1 | 1 | 100.00 | NULL |
    | 2 | MATERIALIZED | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 1000 | 100.00 | Using index |
    +——+———————+——————-+——————+————+———————-+——————+————-+————————-+———+—————+——————-+

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

(10)UNCACHEABLE SUBQUERY

  • 不常用

(11)UNCACHEABLE UNION

  • 不常用

1.4 partitions

  • 分区。一般情况下都为NULL

1.5 type

  • type列就表明了该查询语句使用了什么访问方法,所有的类型如下

    ①system
    ②const
    ③eq_ref
    ④ref
    ⑤fulltext
    ⑥ref_or_null

(1)system

  • 当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system

    mysql> CREATE TABLE t(i int) Engine=MyISAM;
    Query OK, 0 rows affected (0.02 sec)

    mysql> INSERT INTO t VALUES(1);
    Query OK, 1 row affected (0.00 sec)

    mysql> EXPLAIN SELECT * FROM t;
    +——+——————-+———-+——————+————+———————-+———+————-+———+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+————+———————-+———+————-+———+———+—————+———-+
    | 1 | SIMPLE | t | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+————+———————-+———+————-+———+———+—————+———-+

(2)const

  • 当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE id = 5;
    +——+——————-+———-+——————+———-+———————-+————-+————-+———-+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+————-+————-+———-+———+—————+———-+
    | 1 | SIMPLE | s1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+———-+———————-+————-+————-+———-+———+—————+———-+

  1. mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key2 = 5;
  2. +----+-------------+-------+------------+-------+---------------+----------+---------+-------+------+----------+-------+
  3. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  4. +----+-------------+-------+------------+-------+---------------+----------+---------+-------+------+----------+-------+
  5. | 1 | SIMPLE | s1 | NULL | const | idx_key2 | idx_key2 | 5 | const | 1 | 100.00 | NULL |
  6. +----+-------------+-------+------------+-------+---------------+----------+---------+-------+------+----------+-------+

(3)eq_ref

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

    mysql> EXPLAIN SELECT * FROM single_table s1 INNER JOIN single_table s2 ON s1.id = s2.id;
    +——+——————-+———-+——————+————+———————-+————-+————-+———————-+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+————+———————-+————-+————-+———————-+———+—————+———-+
    | 1 | SIMPLE | s1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | test123.s1.id | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+————+———————-+————-+————-+———————-+———+—————+———-+

(4)ref

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

    mysql> EXPLAIN SELECT * FROM single_table s1 where s1.key1=’1’;
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+———-+
    | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+———-+

(5)fulltext

  • 全文索引

(6)ref_or_null

  • 当对普通二级索引进行等值匹配查询,索引列的值=A or null,则对该表的访问方法可能为ref_or_null

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 = ‘a’ OR key1 IS NULL;
    +——+——————-+———-+——————+——————-+———————-+—————+————-+———-+———+—————+———————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+——————-+———————-+—————+————-+———-+———+—————+———————————-+
    | 1 | SIMPLE | s1 | NULL | ref_or_null | idx_key1 | idx_key1 | 303 | const | 2 | 100.00 | Using index condition |
    +——+——————-+———-+——————+——————-+———————-+—————+————-+———-+———+—————+———————————-+

(7)index_merge

  • 一般情况下对于某个表的查询只能用到一个索引,但在某些场景下可以使用Intersection、 Union、 Sort-Union这三种索引合并的 方式来执行查询

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 = ‘a’ OR key3 = ‘a’;
    +——+——————-+———-+——————+——————-+—————————-+—————————-+————-+———+———+—————+——————————————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+——————-+—————————-+—————————-+————-+———+———+—————+——————————————————————-+
    | 1 | SIMPLE | s1 | NULL | index_merge | idx_key1,idx_key3 | idx_key1,idx_key3 | 303,303 | NULL | 2 | 100.00 | Using union(idx_key1,idx_key3); Using where |
    +——+——————-+———-+——————+——————-+—————————-+—————————-+————-+———+———+—————+——————————————————————-+

(8)unique_subquery

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key3 = ‘a’ or key2 IN (SELECT id FROM single_table s2 where s1.key1 = s2.key1);
    +——+——————————+———-+——————+————————-+—————————+————-+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————————+———-+——————+————————-+—————————+————-+————-+———+———+—————+——————-+
    | 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 2 | DEPENDENT SUBQUERY | s2 | NULL | unique_subquery | PRIMARY,idx_key1 | PRIMARY | 4 | func | 1 | 10.00 | Using where |
    +——+——————————+———-+——————+————————-+—————————+————-+————-+———+———+—————+——————-+

(9)index_subquery

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key3 = ‘a’ or common_field IN (SELECT key3 FROM single_table s2 where s1.key1 = s2.key1);
    +——+——————————+———-+——————+———+—————————-+—————+————-+————————-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————————+———-+——————+———+—————————-+—————+————-+————————-+———+—————+——————-+
    | 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 2 | DEPENDENT SUBQUERY | s2 | NULL | index_subquery | idx_key1,idx_key3 | idx_key1 | 303 | test123.s1.key1 | 1 | 10.00 | Using where |
    +——+——————————+———-+——————+———+—————————-+—————+————-+————————-+———+—————+——————-+

(10)range

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 IN (‘a’, ‘b’, ‘c’);
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 3 | 100.00 | Using index condition |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1>’a’ and key1<’d’;
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 1 | 100.00 | Using index condition |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+

(11)index

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

    mysql> EXPLAIN SELECT key_part2 FROM single_table s1 WHERE key_part3 = ‘a’;
    +——+——————-+———-+——————+———-+———————-+———————+————-+———+———+—————+—————————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+———————+————-+———+———+—————+—————————————+
    | 1 | SIMPLE | s1 | NULL | index | NULL | idx_key_part | 909 | NULL | 1000 | 10.00 | Using where; Using index |
    +——+——————-+———-+——————+———-+———————-+———————+————-+———+———+—————+—————————————+

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

  • 对于使用InnoDB存储引擎的表来说,二级索引的记录只包含索引列和主键列的值,而聚簇索引中包含用户定义的全部列以及一些隐藏列,所以扫描二级索引的代价比直接全表扫描,也就是扫描聚簇索引的代价更低

(12)All

  • 全表扫描

    mysql> EXPLAIN SELECT * FROM single_table s1;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———-+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———-+—————+———-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 20396 | 100.00 | NULL |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———-+—————+———-+

1.6 possible_keys和key

  • possible_keys列表示在某个查询语句中,对某个表执行单表查询时【可能用到的索引】有哪些
  • key列表示【实际用到的索引】有哪些

    (1)
    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 > ‘z’ AND key3 = ‘a’;
    +——+——————-+———-+——————+———+—————————-+—————+————-+———-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+—————————-+—————+————-+———-+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | ref | idx_key1,idx_key3 | idx_key3 | 303 | const | 1 | 5.00 | Using where |
    +——+——————-+———-+——————+———+—————————-+—————+————-+———-+———+—————+——————-+

    possible_keys列的值是idx_key1,idx_key3,表示该查询可能使用到idx_key1,idx_key3两个索引
    然后key列的值是idx_key3,表示经过查询优化器计算不同索引的成本后,最后决定使用最优的idx_key3

    (2)对于使用type=Index的,possible_keys为NULL,key为真实使用的索引
    mysql> EXPLAIN SELECT key_part2 FROM single_table s1 WHERE key_part3 = ‘a’;
    +——+——————-+———-+——————+———-+———————-+———————+————-+———+———+—————+—————————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+———————+————-+———+———+—————+—————————————+
    | 1 | SIMPLE | s1 | NULL | index | NULL | idx_key_part | 909 | NULL | 1000 | 10.00 | Using where; Using index |
    +——+——————-+———-+——————+———-+———————-+———————+————-+———+———+—————+—————————————+

  • possible_keys列中的值并不是越多越好,possible_keys越多,查询优化器计算就得花费更多时间比对不同索引的成本, 所以如果可以的话,【尽量删除那些用不到的索引】

1.7 key_len

  • key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,它是由这三个部分构成的:

(1)索引长度

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

(2)如果索引列可以存储NULL值,则要多1个字节

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

  1. 1)由于id列的类型是INT,并且不可以存储NULL值,所以在使用该列的索引时key_len大小就是4
  2. mysql> EXPLAIN SELECT * FROM single_table s1 WHERE id = 5;
  3. +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
  4. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  5. +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
  6. | 1 | SIMPLE | s1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
  7. +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
  8. 2)当索引可以存储NULL值时,key_len=4+1=5
  9. mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key2 = 5;
  10. +----+-------------+-------+------------+-------+---------------+----------+---------+-------+------+----------+-------+
  11. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  12. +----+-------------+-------+------------+-------+---------------+----------+---------+-------+------+----------+-------+
  13. | 1 | SIMPLE | s1 | NULL | const | idx_key2 | idx_key2 | 5 | const | 1 | 100.00 | NULL |
  14. +----+-------------+-------+------------+-------+---------------+----------+---------+-------+------+----------+-------+
  15. 3)对于可变长度的索引。
  16. 由于key1列的类型是VARCHAR(100),所以该列实际最多占用的存储空间就是300字节(100*3字节),
  17. 又因为该列允许存储NULL值,所以key_len需要加1
  18. 又因为该列是可变长度列,所以key_len需要加2
  19. 所以最后ken_len的值就是303
  20. mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 = 'a';
  21. +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
  22. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  23. +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
  24. | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 1 | 100.00 | NULL |
  25. +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
  26. 4)联合索引只用到了idx_key_part的一个索引列,所以为key_len=303
  27. mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key_part1 = 'a';
  28. +----+-------------+-------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
  29. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  30. +----+-------------+-------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
  31. | 1 | SIMPLE | s1 | NULL | ref | idx_key_part | idx_key_part | 303 | const | 1 | 100.00 | NULL |
  32. +----+-------------+-------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
  33. 4)联合索引只用到了idx_key_part2个索引列,所以为key_len=303*2=606
  34. mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key_part1 = 'a' and key_part2 = 'b';
  35. +----+-------------+-------+------------+------+---------------+--------------+---------+-------------+------+----------+-------+
  36. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  37. +----+-------------+-------+------------+------+---------------+--------------+---------+-------------+------+----------+-------+
  38. | 1 | SIMPLE | s1 | NULL | ref | idx_key_part | idx_key_part | 606 | const,const | 1 | 100.00 | NULL |
  39. +----+-------------+-------+------------+------+---------------+--------------+---------+-------------+------+----------+-------+

1.7 ref

  • 当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery时,ref列展示的就是与索引列作等值匹配的东东是什么,比如只是一个常数或者是某个列

    (1)ref=const,表明在使用key=idx_key1时,与key1列做等值匹配的对象是一个常数
    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 = ‘a’;
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+———-+
    | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+———-+

    (2)
    被驱动表s2的访问方法是eq_ref,对应的ref列的值是test123.s1.id,这说明在对被驱动表进行访问时会用到PRIMARY索引
    也就是聚簇索引与一个列进行等值匹配的条件,与s2表的id作等值匹配的对象就是test123.s1.id列
    mysql> EXPLAIN SELECT * FROM single_table s1 INNER JOIN single_table s2 ON s1.id = s2.id;
    +——+——————-+———-+——————+————+———————-+————-+————-+———————-+———+—————+———-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+————+———————-+————-+————-+———————-+———+—————+———-+
    | 1 | SIMPLE | s1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | test123.s1.id | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+————+———————-+————-+————-+———————-+———+—————+———-+

    (3)与索引列进行等值匹配的对象是一个函数,ref=func
    mysql> EXPLAIN SELECT * FROM single_table s1 INNER JOIN single_table s2 ON s2.key1 = UPPER(s1.key1);
    +——+——————-+———-+——————+———+———————-+—————+————-+———+———+—————+———————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+—————+————-+———+———+—————+———————————-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | ref | idx_key1 | idx_key1 | 303 | func | 1 | 100.00 | Using index condition |
    +——+——————-+———-+——————+———+———————-+—————+————-+———+———+—————+———————————-+

1.8 rows

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

    (1)rows列的值是266,这意味着查询优化器在经过分析使用idx_key1进行查询的成本之后,觉得满足key1>’z’这个条件的记录只有266条
    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 > ‘z’;
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 266 | 100.00 | Using index condition |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+

1.9 filtered

MySQL在计算驱动表扇出时采用的一个策略:

  • 如果使用的是全表扫描,那么计算驱动表扇出时需要估计出满足搜索条件的记录到底有多少条
  • 如果使用的是索引,那么计算驱动表扇出时需要估计出除索引搜索条件外的其他搜索条件记录有多少条

    (1)
    从key=idx_key1,知道该查询使用idx_key1执行查询
    从rows=266,知道满足key1>’z’的记录有266条
    filtered=10,表示查询优化器预测在这266条记录中,有10.00%(也就是26610%=26.6条记录)的记录满足其余的搜索条件,也就是common_field = ‘a’这个条件
    mysql> EXPLAIN SELECT
    FROM single_table s1 WHERE key1 > ‘z’ AND common_field = ‘a’;
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————————————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————————————————+
    | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 266 | 10.00 | Using index condition; Using where |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————————————————+

    【对单表查询来说,filtered没什么意义,我们更关注链接查询中,驱动表的filtered值】

    (2)
    s1为驱动表,s2为被驱动表
    s1的rows=1000,filtered=10,则意味着s1的扇出值为100010%=100,即还需要对被驱动表s2执行100次查询
    mysql> EXPLAIN SELECT
    FROM single_table s1 INNER JOIN single_table s2 ON s1.key1 = s2.key1 WHERE s1.common_field = ‘a’;
    +——+——————-+———-+——————+———+———————-+—————+————-+————————-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+—————+————-+————————-+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 1000 | 10.00 | Using where |
    | 1 | SIMPLE | s2 | NULL | ref | idx_key1 | idx_key1 | 303 | test123.s1.key1 | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+———+———————-+—————+————-+————————-+———+—————+——————-+

1.10 Extra

  • Extra列是用来说明一些额外信息的,我们可以通过这些额外信息来更准确的理解MySQL如何执行给定的查询语句
  • 以下是一部分平时常见的extra

(1)No tables used

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

    mysql> EXPLAIN SELECT 1;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+
    | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+

(2)Impossible WHERE

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE 1 != 1;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+—————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+—————————+
    | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+—————————+

(3)No matching min/max row

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

    mysql> EXPLAIN SELECT MIN(key1) FROM single_table s1 WHERE key1 = ‘abcdefg’;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————————-+
    | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No matching min/max row |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————————-+

(4)Using index

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

    (1)下边这个查询中只需要用到idx_key1而不需要回表操作
    mysql> EXPLAIN SELECT key1 FROM single_table s1 WHERE key1 = ‘a’;
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 1 | 100.00 | Using index |
    +——+——————-+———-+——————+———+———————-+—————+————-+———-+———+—————+——————-+

(5)Using index condition

  • 对于正常流程来说,是先查key1>’z’,然后回表,再过滤ey1 LIKE ‘%a’
  • 而mysql会对下述情况进行改进,先查key1>’z’同时判断是否满足key1 LIKE ‘%a’,完成全部后,再进行回表。该操作就称为索引条件下推(Index Condition Pushdown)
  • 应用了Index Condition Pushdown的,Extra就显示Using index condition

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 > ‘z’ AND key1 LIKE ‘%a’;
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+
    | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 2 | 100.00 | Using index condition |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+———————————-+

(6)Using where

  • 当使用全表扫描,并且where有针对该表的搜索条件时,则在Extra列中会提示上述额外信息

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE common_field = ‘a’;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————-+

  • 当使用索引,where除了有索引列搜索条件,还有其他条件,也会显示Using where

    虽然使用idx_key1索引执行查询,但是搜索条件中除了包含key1的搜索条件key1 = ‘a’,
    还有包含common_field的搜索条件,所以Extra列会显示Using where
    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 =2 AND common_field = ‘a’;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 1000 | 1.00 | Using where |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————-+

(7)Using join buffer (Block Nested Loop)

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

    mysql> EXPLAIN SELECT * FROM single_table s1 INNER JOIN single_table s2 ON s1.common_field = s2.common_field;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————————————————————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————————————————————————+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————————————————————————+

    (1)Using join buffer (Block Nested Loop):因为对表s2的访问不能有效利用索引,只好退而求其次,使用join buffer来减少对s2表的访问次数,从而提高性能

    (2)Using where:查询语句中有s1.common_field = s2.common_field条件,
    因为s1是驱动表,s2是被驱动表,所以在访问s2表时,s1.common_field的值已经确定下来了,
    所以实际上查询s2表的条件就是s2.common_field = 一个常数,
    所以提示了Using where额外信息

(8)Not exists

  • 外连接(如左连接),where条件含有被驱动表的非null字段但where is null的,则会显示Not exists

    mysql> EXPLAIN SELECT * FROM single_table s1 LEFT JOIN single_table s2 ON s1.key1 = s2.key1 where s2.id is null;
    +——+——————-+———-+——————+———+———————-+—————+————-+————————-+———+—————+————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+—————+————-+————————-+———+—————+————————————-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
    | 1 | SIMPLE | s2 | NULL | ref | idx_key1 | idx_key1 | 303 | test123.s1.key1 | 1 | 10.00 | Using where; Not exists |
    +——+——————-+———-+——————+———+———————-+—————+————-+————————-+———+—————+————————————-+

    s2.id is null只有在s1与s2的on匹配不上时才能有作用效果,
    也就是,当on条件(s1.key1 = s2.key1)匹配不到结果(not exists)时

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

  • Using intersect(…):说明准备使用Intersect索引合并的方式执行查询,括号中的…表示需要进行索引合并的索引名称
  • Using union(…):说明准备使用Union索引合并的方式执行查询
  • Using sort_union(…):说明准备使用Sort-Union索引合并的方式执行查询

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 =’a’ or key3 = ‘a’;
    +——+——————-+———-+——————+——————-+—————————-+—————————-+————-+———+———+—————+——————————————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+——————-+—————————-+—————————-+————-+———+———+—————+——————————————————————-+
    | 1 | SIMPLE | s1 | NULL | index_merge | idx_key1,idx_key3 | idx_key1,idx_key3 | 303,303 | NULL | 2 | 100.00 | Using union(idx_key1,idx_key3); Using where |
    +——+——————-+———-+——————+——————-+—————————-+—————————-+————-+———+———+—————+——————————————————————-+

(10)Zero limit

  • 当limit=0时,表示不打算从表中读出任何记录,则会提示Zero limit

    mysql> EXPLAIN SELECT * FROM single_table s1 LIMIT 0;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————+
    | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Zero limit |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+——————+

(11)Using filesort

  • 有一些情况下对结果集中的记录进行排序是可以使用到索引的

    mysql> EXPLAIN SELECT * FROM single_table s1 ORDER BY key1 LIMIT 10;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+
    | 1 | SIMPLE | s1 | NULL | index | NULL | idx_key1 | 303 | NULL | 10 | 100.00 | NULL |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+

  • 很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,这种在内存中或者磁盘上进行排序的方式统称为文件排序(filesort)

    mysql> EXPLAIN SELECT * FROM single_table s1 ORDER BY common_field LIMIT 10;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using filesort |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+

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

(12)Using temporary

  • 在许多查询中,MySQL可能会借助【临时表】来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含DISTINCT、GROUP BY、 UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询
  • 如果查询中使用到了内部的临时表,则Extra=Using temporary

    mysql> EXPLAIN SELECT DISTINCT common_field FROM single_table s1;
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————-+
    | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using temporary |
    +——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————-+

  1. //group by查询会默认添加order by,所以有Using filesort
  2. mysql> EXPLAIN SELECT common_field, COUNT(*) AS amount FROM single_table s1 GROUP BY common_field;
  3. +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
  4. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  5. +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
  6. | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using temporary; Using filesort |
  7. +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
  • using temporary需要建立与维护临时表,所以最好用索引替代

    mysql> EXPLAIN SELECT key1, COUNT(*) AS amount FROM single_table s1 GROUP BY key1;
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+
    | 1 | SIMPLE | s1 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 1000 | 100.00 | Using index |
    +——+——————-+———-+——————+———-+———————-+—————+————-+———+———+—————+——————-+

(13)Start temporary, End temporary

  • 通过建立临时表来实现为外层查询中的记录进行去重操作时,驱动表查询执行计划的Extra列将显示Start temporary提示,被驱动表查询执行计划的Extra列将显示End temporary提示

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key1 IN (SELECT key3 FROM single_table s2 WHERE common_field = ‘a’);
    +——+———————+——————-+——————+————+———————-+——————+————-+————————-+———+—————+——————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+———————+——————-+——————+————+———————-+——————+————-+————————-+———+—————+——————-+
    | 1 | SIMPLE | s2 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 10.00 | Using where; Start temporary |
    | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | test123.s2.key3 | 1 | 100.00 | End temporary |
    +——+———————+——————-+——————+————+———————-+——————+————-+————————-+———+—————+——————-+

(14)LooseScan

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

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE key3 IN (SELECT key1 FROM single_table s2 WHERE key1 > ‘z’);
    +——+——————-+———-+——————+———-+———————-+—————+————-+————————-+———+—————+——————————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———-+———————-+—————+————-+————————-+———+—————+——————————————————-+
    | 1 | SIMPLE | s2 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 1 | 100.00 | Using where; Using index; LooseScan |
    | 1 | SIMPLE | s1 | NULL | ref | idx_key3 | idx_key3 | 303 | test123.s2.key1 | 1 | 100.00 | NULL |
    +——+——————-+———-+——————+———-+———————-+—————+————-+————————-+———+—————+——————————————————-+

(15)FirstMatch(tbl_name)

  • 在将In子查询转为semi-join时,如果采用的是FirstMatch执行策略,则Extra=FirstMatch(tbl_name)

    mysql> EXPLAIN SELECT * FROM single_table s1 WHERE common_field IN (SELECT key1 FROM single_table s2 where s1.key3 = s2.key3);
    +——+——————-+———-+——————+———+—————————-+—————+————-+—————————-+———-+—————+——————————————-+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +——+——————-+———-+——————+———+—————————-+—————+————-+—————————-+———-+—————+——————————————-+
    | 1 | SIMPLE | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 1000 | 100.00 | Using where |
    | 1 | SIMPLE | s2 | NULL | ref | idx_key1,idx_key3 | idx_key3 | 303 | test123.s1.key3 | 1 | 4.42 | Using where; FirstMatch(s1) |
    +——+——————-+———-+——————+———+—————————-+—————+————-+—————————-+———-+—————+—————————————

发表评论

表情:
评论列表 (有 0 条评论,303人围观)

还没有评论,来说两句吧...

相关阅读

    相关 explain详解

    explain是干嘛的? 使用explain可以模拟优化器执行sql语句,分析查询语句的结构,是否使用索引等等 使用方法: 在查询语句select关键字前面加上expl

    相关 MySQL Explain详解

    最近慢慢接触MySQL,了解如何优化它也迫在眉睫了,话说工欲善其事,必先利其器。最近我就打算了解下几个优化MySQL中经常用到的工具。今天就简单介绍下EXPLAIN。 内容

    相关 mysql explain详解

    explain显示了mysql如何使用索引来处理select语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。 先解析一条sql语句,看出现什么内容 `EXPL