掘金 后端 ( ) • 2024-05-02 17:57

explain第二弹

1.explain是什么

条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划,这个执行计划展示了接下来具体执行查询的方式,

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

2.id列

总结:一个select一个id,id相同了说明是在同一个select

在一个大的查询语句中每个SELECT关键字都对应一个唯一的id

比如 EXPLAIN SELECT * FROM s1 INNER JOIN 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 | 9688 |   100.00 | NULL                                  |
|  1 | SIMPLE      | s2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9954 |   100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+

比如 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a');

mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM 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              | 9954 |    10.00 | Using where; Start temporary |
|  1 | SIMPLE      | s1    | NULL       | ref  | idx_key1      | idx_key1 | 303     | s2.key3 |    1 |   100.00 | End temporary                |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+
2 rows in set, 1 warning (0.00 sec)

看起来是两个select应该有两个不同的id ,但是但是执行计划中s1s2表对应的记录的id值全部是1,这就表明了查询优化器将子查询转换为了连接查询(也就是连表)。

3.select_type列

意义:每一个SELECT关键字代表的小查询 这个小查询在整个大查询中扮演了一个什么角色

名称 描述 SIMPLE 查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型,但是可能包括连表 PRIMARY 对于包含UNIONUNION ALL或者子查询的大查询来说,最左边的那个查询的select_type值就是PRIMARY UNIONUNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type值就是UNION UNION RESULT MySQL选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT SUBQUERY 如果包含子查询的查询语句不能够转为对应的semi-join(优化成连表)的形式 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'; DEPENDENT UNION 在包含UNION或者UNION ALL的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的select_type的值就是DEPENDENT UNIONDERIVED Derived 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)

4.partitions列

一般为null 不介绍

5.type列

一条记录就代表着MySQL对某个表的执行查询时的访问方法,其中的type列就表明了这个访问方法是什么

性能从高到低:

名称 描述 system 当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory const 根据主键或者唯一二级索引列与常数进行等值匹配时。就是直接一步走索引的 eq_ref 在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的 ref 当通过普通的二级索引列与常量进行等值匹配时来查询某个表,就是先走二级索引,再等值去主索引 ref_or_null 当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL index_merge 索引合并的方法,EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a' unique_subquery index_subquery range 如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法() index 遍历二级索引,当我们可以使用索引覆盖,但需要扫描全部的索引记录时,比如:SELECT key_part1, key_part2, key_part3 FROM single_table WHERE key_part2 = 'abc'; ALL 全表

一些例子 进行说明:

  • eq_ref

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

mysql> EXPLAIN SELECT * FROM s1 INNER JOIN 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            | 9688 |   100.00 | NULL  |
|  1 | SIMPLE      | s2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | xiaohaizi.s1.id |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------------+------+----------+-------+
2 rows in set, 1 warning (0.01 sec)

从执行计划的结果中可以看出,MySQL打算将s1作为驱动表,s2作为被驱动表,重点关注s2的访问方法是eq_ref,表明在访问s2表的时候可以通过主键的等值匹配来进行访问。

6.possible_keys和key列

possible_keys:对某个表执行单表查询时可能用到的索引有哪些,key:表示实际用到的索引有哪些

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

还有可能possible_keys为空,key为某个索引

7.key_len列

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

  • 对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。
  • 如果该索引列可以存储NULL值,则key_len比不可以存储NULL值时多1个字节。
  • 对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度。

8.ref列

反应的是type的表访问方法

是在访问方法是consteq_refrefref_or_nullunique_subqueryindex_subquery其中之一时,ref列展示的就是与索引列作等值匹配的东东是什么,比如只是一个常数(const)或者是某个列。

9.rows列

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

10.filtered列

  • 如果使用的是全表扫描的方式执行的单表查询,那么计算驱动表扇出时需要估计出满足搜索条件的记录到底有多少条。
  • 如果使用的是索引执行的单表扫描,那么计算驱动表扇出的时候需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。

单表看这个一般没用 更关注在连接查询中驱动表对应的执行计划记录的filtered

11.extra列

顾名思义,额外信息

这里只列一些常用的

  • Using index

    查询以及搜索条件只包含属于某个索引的列(索引覆盖)

  • Using index condition

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b';
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
      | 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 row in set, 1 warning (0.01 sec)
    

    有些搜索条件中虽然出现了索引列,但却不能完全使用索引

    比如上面的sql, 先根据key1 > 'z'这个条件,从二级索引idx_key1中获取到对应的二级索引记录,先不着急回表,而是先检测一下该记录是否满足key1 LIKE '%a',再回表(他们的这个改进称之为索引条件下推(英文名:Index Condition Pushdown)也就是Using index condition

  • Using where

    全表扫描并且,该语句的WHERE子句中有针对该表的搜索条件,或者是走了索引,但是还需要再过滤一次

12.执行顺序

id相同:执行顺序由上至下

id不同,id值越大优先级越高,越先被执行(子查询先查)

拓展

查看执行计划的成本

EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a'

中间加上FORMAT=JSON

optimizer trace表

MySQL 5.6以及之后的版本,查看优化器生成执行计划的整个过程

  • 使用前需要打开系统变量

参考