【MySQL01】【 Explain 命令详解】

文章目录

  • 一、前言
  • 二、Explain 概览
  • 三、Explain 详解
    • 1. id
    • 2. select_type
    • 3. table
    • 4. type
    • 5. possible_keys
    • 6. key
    • 7. key_len
    • 8. ref
    • 9. rows
    • 10. filtered
    • 11. extra 列
  • 四、补充
    • 1. EXPLAIN 扩展
      • 1.1 Extend EXPLAIN
      • 1.2 JSON 格式的执行计划
    • 2. Intersection、Union、Sort-Union 索引合并
      • 2.1 Intersection
      • 2.2 Union
      • 2.3 Sort-Union
    • 3. 基于块的嵌套循环连接
  • 五、参考内容

一、前言

最近在读《MySQL 是怎样运行的》一书,后续会随机将书中部分内容记录下来作为学习笔记,部分内容经过个人删改,因此可能存在错误,如想详细了解相关内容强烈推荐阅读该书。


本文 Mysql 版本为 5.7.16,

涉及表结构来源两处:

  1. 数据库 基于 Mysql 官方提供的 sakila 内容基于 下载地址:https://dev.mysql.com/doc/index-other.html
    在这里插入图片描述

  2. 如下:

    CREATE TABLE `t1` (`id` int(11) NOT NULL,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,`c` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_a` (`a`),KEY `idx_b` (`b`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `t2` (`id` int(11) NOT NULL,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,`c` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),KEY `a` (`a`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

二、Explain 概览

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。 通过explain我们可以获得以下信息:

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询

使用方法:explain + sql语句。 如下

在这里插入图片描述
可以看到下面我们来分析每一列的内容

列名描述
id选择标识符。数字越大,越先执行;数字相同,从上往下依次执行。需要注意在连接查询(join )中由于每个表都会对应一条记录,因此会存在多条 ID 相同的记录,此时出现在前面的表示驱动表,出现在后面的表示被驱动表。
select_type表示查询的类型
table输出结果集的表。EXPLAIN 语句输出的每条记录都对应着某个单表的访问方法,table 即为该表的表名
partitions匹配的分区
type表示表的连接类型
possible_keys表示查询时,可能使用的索引
key表示实际使用的索引
key_len索引字段的长度
ref列与索引的比较
rows扫描出的行数(估算的行数)
filtered按表条件过滤的行百分比
Extra执行情况的描述和说明

三、Explain 详解

1. id

选择标识符。数字越大,越先执行;数字相同,从上往下依次执行。需要注意在连接查询(join )中由于每个表都会对应一条记录,因此会存在多条 ID 相同的记录,此时出现在前面的表示驱动表,出现在后面的表示被驱动表。

  1. id 不同 : ID不同,数字越大,越先执行。 如下, 先执行SUBQUERY,再执行 PRIMARY 。

    mysql> explain select * from film where film_id = (select max(film_id) from film);
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+------------------------------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra                        |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+------------------------------+
    |  1 | PRIMARY     | film  | NULL       | const | PRIMARY       | PRIMARY | 2       | const |    1 |   100.00 | NULL                         |
    |  2 | SUBQUERY    | NULL  | NULL       | NULL  | NULL          | NULL    | NULL    | NULL  | NULL | NULL     | Select tables optimized away |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+------------------------------+
    2 rows in set (0.08 sec)
    
  2. id 相同 :按照从上到下的顺序依次执行

    mysql> explain select f.* from film f left join film_actor fa on fa.film_id = f.film_id;
    +----+-------------+-------+------------+------+----------------+----------------+---------+------------------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys  | key            | key_len | ref              | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+----------------+----------------+---------+------------------+------+----------+-------------+
    |  1 | SIMPLE      | f     | NULL       | ALL  | NULL           | NULL           | NULL    | NULL             | 1000 |   100.00 | NULL        |
    |  1 | SIMPLE      | fa    | NULL       | ref  | idx_fk_film_id | idx_fk_film_id | 2       | sakila.f.film_id |    5 |   100.00 | Using index |
    +----+-------------+-------+------------+------+----------------+----------------+---------+------------------+------+----------+-------------+
    2 rows in set (0.06 sec)
    
  3. id为null : 表示一个结果集,并不需要使用它来进行查询。

    UNION 会将多个查询的结果集合合并起来并对结果集中的记录去重,去重的实现是通过 MySQL 的 内部临时表,如下即使将 id 为 1 和 2 的查询结果集合并起来去重,在内部创建了一个名为 <union1,2> 的零时表,id 为 null 说明这个临时表是为了合并两个查询的结果集而创建的 。额外的,由于 union all 不需要对结果集去重,他只是单纯的将结果集合并后返回给用户也就不需要临时表,因此就没有 id = NULL 的记录。

    -- 如下 UNION RESULT 作为一个合并结果集的操作的执行id为null。
    mysql> explain select film_id from film union select film_id from film_actor;
    +------+--------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------+
    | id   | select_type  | table      | partitions | type  | possible_keys | key                | key_len | ref  | rows | filtered | Extra           |
    +------+--------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------+
    |    1 | PRIMARY      | film       | NULL       | index | NULL          | idx_fk_language_id | 1       | NULL | 1000 |   100.00 | Using index     |
    |    2 | UNION        | film_actor | NULL       | index | NULL          | idx_fk_film_id     | 2       | NULL | 5462 |   100.00 | Using index     |
    | NULL | UNION RESULT | <union1,2> | NULL       | ALL   | NULL          | NULL               | NULL    | NULL | NULL | NULL     | Using temporary |
    +------+--------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------+
    3 rows in set (0.03 sec)-- 额外的,由于 union all 不需要对结果集去重,他只是单纯的将结果集合并后返回给用户也就不需要临时表,因此就没有 id = NULL 的记录。
    mysql> explain select * from t1 union all select * from t2;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | PRIMARY     | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL  |
    |  2 | UNION       | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    2 rows in set (0.04 sec)
    

需要注意的是:

  1. 在连接查询(join )中由于每个表都会对应一条记录,因此会存在多条 ID 相同的记录,此时出现在前面的表示驱动表,出现在后面的表示被驱动表。

    -- 如下:t1在t2 之前,所以 t1 表将作为驱动表,t2 表将作为被驱动表
    mysql> explain select * from t1 inner join t2;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                 |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL                                  |
    |  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | Using join buffer (Block Nested Loop) |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
    2 rows in set (0.02 sec)
    
  2. 查询优化器可能对涉及子查询的查询语句重写,从而转换为连接查询,如果想知道查询优化器是否对某个包含子查询的语句进行了重写,可以直接看执行计划。如下:

    -- 这一句由于包含 or id = 1 的部分,因此无法转换为 连接查询,所以存在 DEPENDENT SUBQUERY, id 列的值不同,说明存在执行优先级
    mysql> explain select * from t1 where id in (select id from t2) or id = 1;
    +----+--------------------+-------+------------+-----------------+---------------+---------+---------+------+------+----------+-------------+
    | id | select_type        | table | partitions | type            | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
    +----+--------------------+-------+------------+-----------------+---------------+---------+---------+------+------+----------+-------------+
    |  1 | PRIMARY            | t1    | NULL       | ALL             | PRIMARY       | NULL    | NULL    | NULL |  100 |   100.00 | Using where |
    |  2 | DEPENDENT SUBQUERY | t2    | NULL       | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 |   100.00 | Using index |
    +----+--------------------+-------+------------+-----------------+---------------+---------+---------+------+------+----------+-------------+
    2 rows in set (0.03 sec)
    -- 这一句可以直接转换为连接查询,所以id 列相同。
    mysql> explain select * from t1 where id in (select id from t2);
    +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
    | id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref        | rows | filtered | Extra       |
    +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL       |  100 |   100.00 | NULL        |
    |  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | demo.t1.id |    1 |   100.00 | Using index |
    +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
    2 rows in set (0.04 sec)
    

2. select_type

select_type 代表查询的类型, 各种取值如下:

  1. SIMPLE: 即简单查询,查询语句中不包含 UNION 或者子查询的查询都算做 SIMPLE 类型。

    mysql> explain select * from film;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | film  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    1 row in set (0.05 sec)mysql> explain select f.* from film f left JOIN film_actor fa on fa.film_id = f.film_id;
    +----+-------------+-------+------------+------+----------------+----------------+---------+------------------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys  | key            | key_len | ref              | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+----------------+----------------+---------+------------------+------+----------+-------------+
    |  1 | SIMPLE      | f     | NULL       | ALL  | NULL           | NULL           | NULL    | NULL             | 1000 |   100.00 | NULL        |
    |  1 | SIMPLE      | fa    | NULL       | ref  | idx_fk_film_id | idx_fk_film_id | 2       | sakila.f.film_id |    5 |   100.00 | Using index |
    +----+-------------+-------+------------+------+----------------+----------------+---------+------------------+------+----------+-------------+
    2 rows in set (0.03 sec)
    
  2. PRIMARY: 对于包含 UNION、UNION ALL 或者子查询的大查询来说,他是由几个小查询组成的。

    mysql> explain select film_id from film union all select film_id from film_actor;
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    | id | select_type | table      | partitions | type  | possible_keys | key                | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    |  1 | PRIMARY     | film       | NULL       | index | NULL          | idx_fk_language_id | 1       | NULL | 1000 |   100.00 | Using index |
    |  2 | UNION       | film_actor | NULL       | index | NULL          | idx_fk_film_id     | 2       | NULL | 5462 |   100.00 | Using index |
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    2 rows in set (0.03 sec)mysql> explain select film_id from film union all select film_id from film_actor where film_id > (select min(film_id) from film_actor);
    +----+-------------+------------+------------+-------+----------------+--------------------+---------+------+------+----------+------------------------------+
    | id | select_type | table      | partitions | type  | possible_keys  | key                | key_len | ref  | rows | filtered | Extra                        |
    +----+-------------+------------+------------+-------+----------------+--------------------+---------+------+------+----------+------------------------------+
    |  1 | PRIMARY     | film       | NULL       | index | NULL           | idx_fk_language_id | 1       | NULL | 1000 |   100.00 | Using index                  |
    |  2 | UNION       | film_actor | NULL       | range | idx_fk_film_id | idx_fk_film_id     | 2       | NULL | 5452 |   100.00 | Using where; Using index     |
    |  3 | SUBQUERY    | NULL       | NULL       | NULL  | NULL           | NULL               | NULL    | NULL | NULL | NULL     | Select tables optimized away |
    +----+-------------+------------+------------+-------+----------------+--------------------+---------+------+------+----------+------------------------------+
    3 rows in set (0.02 sec)
  3. UNION:对于包含 UNION 或者 UNION ALL 的大查询来说,它是由几个小查询组成的;其中除了最左边的那个小查询外,其余小查询的 select_type 的值就是 UNUION。

    mysql> explain select film_id from film union all select film_id from film_actor UNION all select film_id from film_text;
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    | id | select_type | table      | partitions | type  | possible_keys | key                | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    |  1 | PRIMARY     | film       | NULL       | index | NULL          | idx_fk_language_id | 1       | NULL | 1000 |   100.00 | Using index |
    |  2 | UNION       | film_actor | NULL       | index | NULL          | idx_fk_film_id     | 2       | NULL | 5462 |   100.00 | Using index |
    |  3 | UNION       | film_text  | NULL       | index | NULL          | PRIMARY            | 2       | NULL | 1000 |   100.00 | Using index |
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    3 rows in set (0.04 sec)
  4. UNION RESULT : MySQL 选择用临时表来完成 UNION 查询的去重工作,针对该临时表的查询的 select_type 就是 UNION RESULT

    mysql> explain select * from t1 union select * from t2;
    +------+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    | id   | select_type  | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra           |
    +------+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    |    1 | PRIMARY      | t1         | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL            |
    |    2 | UNION        | t2         | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL            |
    | NULL | UNION RESULT | <union1,2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | NULL | NULL     | Using temporary |
    +------+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    3 rows in set (0.06 sec)
    
  5. SUBQUERY : 如果包含子查询的查询语句不能转换为对应的半连接形式,并且该子查询是不相关子查询,而且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个 SELECT 关键字代表的那个查询就是 SUBQUERY。(需要注意: 查询条件是 SUBQUERY 的子查询会被物化,所以该子查询只需要执行一遍)

    mysql> explain SELECT * FROM film_category WHERE film_id = (SELECT max(film_id) FROM film_actor);
    +----+-------------+---------------+------------+------+---------------+---------+---------+-------+------+----------+------------------------------+
    | id | select_type | table         | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra                        |
    +----+-------------+---------------+------------+------+---------------+---------+---------+-------+------+----------+------------------------------+
    |  1 | PRIMARY     | film_category | NULL       | ref  | PRIMARY       | PRIMARY | 2       | const |    1 |   100.00 | Using where                  |
    |  2 | SUBQUERY    | NULL          | NULL       | NULL | NULL          | NULL    | NULL    | NULL  | NULL | NULL     | Select tables optimized away |
    +----+-------------+---------------+------------+------+---------------+---------+---------+-------+------+----------+------------------------------+
    2 rows in set (0.03 sec)mysql> explain SELECT (select 1 from film_category) from film;
    +----+-------------+---------------+------------+-------+---------------+---------------------------+---------+------+------+----------+-------------+
    | id | select_type | table         | partitions | type  | possible_keys | key                       | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+---------------+------------+-------+---------------+---------------------------+---------+------+------+----------+-------------+
    |  1 | PRIMARY     | film          | NULL       | index | NULL          | idx_fk_language_id        | 1       | NULL | 1000 |   100.00 | Using index |
    |  2 | SUBQUERY    | film_category | NULL       | index | NULL          | fk_film_category_category | 1       | NULL | 1000 |   100.00 | Using index |
    +----+-------------+---------------+------------+-------+---------------+---------------------------+---------+------+------+----------+-------------+
    2 rows in set (0.05 sec)
    
  6. DEPENDENT SUBQUERY : 如果包含子查询的查询语句不能够转为对应的半连接形式,并且该子查询被查询优化器转换为相关子查询的形式,则该子查询第一个 SELECT 关键字代表的那个查询就是 DEPENDENT SUBQUERY。需要注意 DEPENDENT SUBQUERY 的子查询可能会被执行多次。

    如下SQL,大查询中包含一个子查询,子查询中又包含由 UNION 连接起来的两个小查询。从执行计划可以看到,select id from t2 where a = '1' 是子查询的第一个查询,所以他的类型是 DEPENDENT SUBQUERY,而 select id from t1 where a = '1' 这个查询就是 DEPENDENT UNION 类型。

    mysql> explain select * from t1 where id in (select id from t2 where a = '1' union select id from t1 where a = '1');
    +------+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+
    | id   | select_type        | table      | partitions | type   | possible_keys | key     | key_len | ref  | rows | filtered | Extra           |
    +------+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+
    |    1 | PRIMARY            | t1         | NULL       | ALL    | NULL          | NULL    | NULL    | NULL |  100 |   100.00 | Using where     |
    |    2 | DEPENDENT SUBQUERY | t2         | NULL       | eq_ref | PRIMARY,a     | PRIMARY | 4       | func |    1 |     5.00 | Using where     |
    |    3 | DEPENDENT UNION    | t1         | NULL       | eq_ref | PRIMARY,a     | PRIMARY | 4       | func |    1 |     5.00 | Using where     |
    | NULL | UNION RESULT       | <union2,3> | NULL       | ALL    | NULL          | NULL    | NULL    | NULL | NULL | NULL     | Using temporary |
    +------+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+
    4 rows in set (0.07 sec)
    
  7. DEPENDENT UNION : 在包含 UNION 或者 UNION ALL的大查询中,如果各个子查询都依赖于外层查询,除了最左边的那个小查询之外,其余小查询就是 DEPENDENT UNION 。

    如下SQL,大查询中包含一个子查询,子查询中又包含由 UNION 连接起来的两个小查询。从执行计划可以看到,select id from t2 where a = '1' 是子查询的第一个查询,所以他的类型是 DEPENDENT SUBQUERY,而 select id from t1 where a = '1' 这个查询就是 DEPENDENT UNION 类型。

    mysql> explain select * from t1 where id in (select id from t2 where a = '1' union select id from t1 where a = '1');
    +------+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+
    | id   | select_type        | table      | partitions | type   | possible_keys | key     | key_len | ref  | rows | filtered | Extra           |
    +------+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+
    |    1 | PRIMARY            | t1         | NULL       | ALL    | NULL          | NULL    | NULL    | NULL |  100 |   100.00 | Using where     |
    |    2 | DEPENDENT SUBQUERY | t2         | NULL       | eq_ref | PRIMARY,a     | PRIMARY | 4       | func |    1 |     5.00 | Using where     |
    |    3 | DEPENDENT UNION    | t1         | NULL       | eq_ref | PRIMARY,a     | PRIMARY | 4       | func |    1 |     5.00 | Using where     |
    | NULL | UNION RESULT       | <union2,3> | NULL       | ALL    | NULL          | NULL    | NULL    | NULL | NULL | NULL     | Using temporary |
    +------+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+
    4 rows in set (0.07 sec)
    
  8. DERIVED : 在包含派生表的查询中,如果是以物化派生表的方式执行查询,则派生表对应的子查询是 DERIVED。

    mysql> explain SELECT ff.* FROM ( SELECT film_id FROM film UNION ALL SELECT film_id FROM film_actor ) ff;
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    | id | select_type | table      | partitions | type  | possible_keys | key                | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    |  1 | PRIMARY     | <derived2> | NULL       | ALL   | NULL          | NULL               | NULL    | NULL | 6462 |   100.00 | NULL        |
    |  2 | DERIVED     | film       | NULL       | index | NULL          | idx_fk_language_id | 1       | NULL | 1000 |   100.00 | Using index |
    |  3 | UNION       | film_actor | NULL       | index | NULL          | idx_fk_film_id     | 2       | NULL | 5462 |   100.00 | Using index |
    +----+-------------+------------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
    3 rows in set (0.02 sec)
    
  9. MATERIALIZED : 当查询优化器在执行包含子查询的语句时,选择将子查询物化后与外层查询进行连接查询,该子查询对应的类型即为 MATERIALIZED 。

    mysql> explain select * from t1 where b in (select b from t2);
    +----+--------------+-------------+------------+--------+---------------+------------+---------+-----------+------+----------+-------------+
    | id | select_type  | table       | partitions | type   | possible_keys | key        | key_len | ref       | rows | filtered | Extra       |
    +----+--------------+-------------+------------+--------+---------------+------------+---------+-----------+------+----------+-------------+
    |  1 | SIMPLE       | t1          | NULL       | ALL    | NULL          | NULL       | NULL    | NULL      |  100 |   100.00 | Using where |
    |  1 | SIMPLE       | <subquery2> | NULL       | eq_ref | <auto_key>    | <auto_key> | 5       | demo.t1.b |    1 |   100.00 | NULL        |
    |  2 | MATERIALIZED | t2          | NULL       | ALL    | NULL          | NULL       | NULL    | NULL      | 1000 |   100.00 | NULL        |
    +----+--------------+-------------+------------+--------+---------------+------------+---------+-----------+------+----------+-------------+
    3 rows in set (0.06 sec)
    

3. table

显式的查询显式查询的表名,如果查询使用了别名,这里显示的则是别名。如果不涉及对数据库的操作,那么这里显示的就是null 。如果显示为 <derived N > 则表明这个是临时表,N为执行计划id,表示这个结果来源于这个查询自身,如果显示为 <union M, N>,其也是一个临时表,表示这个结果来自于 union 查询的 id为 M,N的结果集。


不涉及表操作 :

mysql> explain select sleep(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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set (0.06 sec)

4. type

type列很重要,是用来说明表与表之间是如何进行关联操作的,有没有使用索引。MySQL中“关联”一词比一般意义上的要宽泛,MySQL认为任何一次查询都是一次“关联”,并不仅仅是一个查询需要两张表才叫关联,所以也可以理解MySQL是如何访问表的。主要有下面几种类别。

type 依次性能从好到差 : system,const,eq_ref, ref, ref_or_null, index_merge, unique_subquery, index_subquery, range,index,all。除了all之外,其他type都能用到索引,除了 index_merge 之外,其他的type只可以用到一个索引,一般来说,好的 sql查询至少达到 range 级别,最好能达到 ref

  1. system : 当表中只有一条记录并且该表使用的存储引擎(如 MyISAM、MEMORY)的统计数据是精确的,那么该表的访问方法就是 system。如果是 Innodb 引擎表,type 在这个情况下通常都是 all 或者 index。

  2. const : 当根据主键或者唯一二级索引列与常数进行等值匹配的时候,对单表的访问方法就是 const。即查询是通过主键或唯一二级索引来与常量做比较,最终结果只能有一条记录。

    mysql> explain select * from film where film_id = 1;
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | film  | NULL       | const | PRIMARY       | PRIMARY | 2       | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    1 row in set (0.01 sec)
    
  3. eq_ref : 执行连接查询时,如果被驱动表是通过主键或者不允许存储 NULL 值的唯一二级索引列等值匹配的方式进行访问的(如果该主键或者不允许存储 NULL 值的唯一二级索引是联合索引,则所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是 eq_ref。即查询是通过主键或唯一二级索引来来作为查询条件,最终结果只能有一条记录。

    如下,t1 将作为驱动表,t2 作为被驱动表。在访问 t2 表时,可以通过主键等值匹配来访问:

    mysql> explain select * from t1 inner join t2 on t1.id = t2.id;
    +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
    | id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref        | rows | filtered | Extra |
    +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
    |  1 | SIMPLE      | t1    | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL       |  100 |   100.00 | NULL  |
    |  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | demo.t1.id |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
    2 rows in set (0.07 sec)
    
  4. ref : 当通过普通索引列与常量进行等值匹配的方式来查询某个表时,对该表的访问方法就可能是 ref。另外,如果是执行连接查询,被驱动表中的某个普通的二级索引列与驱动表中的某个列进行等值匹配,那么对被驱动表也可能使用 ref。

    相较于 eq_ref。 ref 不要求记录唯一性,也就是不要求索引是主键或者唯一索引。ref在连接查询时,只要求是索引即可,返回记录也可出现多条。总之,返回数据可能不唯一的等值查找就可能出现。

    mysql> explain select * from film f left join film_actor fa on fa.film_id = f.film_id;
    +----+-------------+-------+------------+------+----------------+----------------+---------+-------------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys  | key            | key_len | ref               | rows | filtered | Extra |
    +----+-------------+-------+------------+------+----------------+----------------+---------+-------------------+------+----------+-------+
    |  1 | SIMPLE      | fa    | NULL       | ALL  | NULL           | NULL           | NULL    | NULL              | 1000 |   100.00 | NULL  |
    |  1 | SIMPLE      | f     | NULL       | ref  | idx_fk_film_id | idx_fk_film_id | 2       | sakila.fa.film_id |    5 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+----------------+----------------+---------+-------------------+------+----------+-------+
    2 rows in set (0.06 sec)mysql> explain select * from film where title = 'film1';
    +----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | film  | NULL       | ref  | idx_title     | idx_title | 514     | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
    1 row in set (0.07 sec)
  5. fulltext : 全文索引检索,全文检索的优先级很高,若全文索引和普通索引同时存在,mysql不管代价,优先选择全文索引。

  6. ref_or_null : 当对普通二级索引列进行等值匹配且该索引列的值也可以是 NULL 值时,对该表的访问方法就可能是 ref_or_null。简单来说类似ref,但是可以搜索值为NULL的行。

    mysql> explain select * from t1 where a = 'a' or a is null;
    +----+-------------+-------+------------+-------------+---------------+-----+---------+-------+------+----------+-----------------------+
    | id | select_type | table | partitions | type        | possible_keys | key | key_len | ref   | rows | filtered | Extra                 |
    +----+-------------+-------+------------+-------------+---------------+-----+---------+-------+------+----------+-----------------------+
    |  1 | SIMPLE      | t1    | NULL       | ref_or_null | a             | a   | 5       | const |    2 |   100.00 | Using index condition |
    +----+-------------+-------+------------+-------------+---------------+-----+---------+-------+------+----------+-----------------------+
    1 row in set (0.06 sec)
    
  7. index_merge : 一般清理下只会为单个索引生成扫描区间,但是 InnoDB 存在Intersection (交集)索引合并、Union (并集)索引合并、Sort-Union 索引合并 三种索引合并的方式来执行查询。(关于这三种索引合并方式,本文末有补充)。

    如下,type 为 index_mege, 即MySQL 打算使用索引合并的方式来执行对 t1 表的查询。

    mysql> explain select * from t1 where a = 'a' or b = 'b';
    +----+-------------+-------+------------+-------------+---------------+-------------+---------+------+------+----------+---------------------------------------+
    | id | select_type | table | partitions | type        | possible_keys | key         | key_len | ref  | rows | filtered | Extra                                 |
    +----+-------------+-------+------------+-------------+---------------+-------------+---------+------+------+----------+---------------------------------------+
    |  1 | SIMPLE      | t1    | NULL       | index_merge | idx_a,idx_b   | idx_a,idx_b | 5,5     | NULL |    2 |   100.00 | Using union(idx_a,idx_b); Using where |
    +----+-------------+-------+------------+-------------+---------------+-------------+---------+------+------+----------+---------------------------------------+
    1 row in set (0.06 sec)
    
  8. unique_subquery : 类似于两表连接中被驱动表的 eq_ref 访问方法, unique_subquery 针对的事一些包含 in 子查询的查询语句。如果查询优化器决定将 in 子查询转换为 exists 子查询,而且子查询在转换之后可以使用主键或者不允许存储 NULL 值的唯一二级索引进行等值匹配,那么该子查询执行计划的 type 列的值就是 unique_subquery 。

    mysql> explain select * from t1 where a in (select id from t2 where t1.id = t2.id) or b = 'a';
    +----+--------------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+--------------------------+
    | id | select_type        | table | partitions | type   | possible_keys | key     | key_len | ref        | rows | filtered | Extra                    |
    +----+--------------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+--------------------------+
    |  1 | PRIMARY            | t1    | NULL       | ALL    | idx_b         | NULL    | NULL    | NULL       |  100 |   100.00 | Using where              |
    |  2 | DEPENDENT SUBQUERY | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | demo.t1.id |    1 |   100.00 | Using where; Using index |
    +----+--------------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+--------------------------+
    2 rows in set (0.06 sec)
    
  9. index_subquery : 与 unique_subquery 类似,只不过在访问子查询中表时使用的是普通索引。

  10. range : 如果使用索引获取某些单点扫描区间的记录,那么就可能使用到 range 访问方法,即索引范围扫描,常见于使用 >, <, is null, between, in, like 等范围运算符查询中。

    mysql> explain select * from t1 where id > 0;
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | t1    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |  100 |   100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    1 row in set (0.06 sec)
    
  11. index : 当可以使用索引覆盖,但需要扫描全部的索引时,该表的访问方法就是 index。 额外的,当我们需要执行全表扫描,并且需求对主键进行排序时,此时的 type 也是 index。

    mysql> explain select id from t1;
    +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | t1    | NULL       | index | NULL          | idx_a | 5       | NULL |  100 |   100.00 | Using index |
    +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
    1 row in set (0.04 sec)
    
  12. all : 全表扫描数据文件,然后再在server层进行过滤返回符合要求的记录。效率最差

    mysql> explain select * from t1;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    1 row in set (0.04 sec)
    

5. possible_keys

表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些,这里可能出现多个的。如下:对于 t1 表 字段a 和 b 都是普通索引,如下语句在执行前就可能会使用 idx_a 或 idx_b 为索引。最终通过查询优化器计算后认为执行全表扫描成本更低(因为表里就一条记录)

mysql> explain select * from t1 where a > 'a' and b > 'b';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_a,idx_b   | NULL | NULL    | NULL |  100 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set (0.04 sec)

6. key

表示在某个查询语句中,对某个表执行单表查询时真正用到的索引有哪些。之所以出现 possible_keys 与 key 的不同是因为MySQL 经过查询优化器计算不同所以的使用成本后,会选择成本较少的索引使用。需要注意的是 :当select_type 为 index_merge 时,这里可能出现两个以上的索引,其他的 select_type 这里只会出现一个

7. key_len

用于处理查询的索引长度,如果是单列索引,那就是整个索引长度,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少列的索引,这里就会计算出去,没有使用到的列不会计算进去。留意下整个列的值,算一下你的多列索引使用长度就知道有没有使用到所有的列。另外 key_len 只计算where 条件用到的索引长度,而 排序和分组就算用到了索引,也不会计算到 key_len 中。

具体的长度计算逻辑在《MySQL是怎样运行的》中有具体示例,如有需要可以看书。

8. ref

当访问方法时 const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery 中的其中一个时,ref 列展示的就是与索引列进行等值匹配的东西,比如只是一常数或者某一列

-- 如下查询:以 t1 表作为驱动表,查询条件是 const 级别, 以 t2 表作为被驱动表,对应的 ref列是 demo.t1.id
mysql> explain select * from t1 inner join t2 on t1.id = t2.id where t1.a = 'a';
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref        | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref    | PRIMARY,idx_a | idx_a   | 5       | const      |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | demo.t1.id |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
2 rows in set (0.05 sec)

需要注意的是有时与索引列做匹配的是一个函数,此时 ref 列展示的是 func,如下:

mysql> explain select * from t1 inner join t2 on t1.id = upper(t2.id);
+----+-------------+-------+------------+--------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | ALL    | NULL          | NULL    | NULL    | NULL | 1000 |   100.00 | NULL        |
|  1 | SIMPLE      | t1    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | func |    1 |   100.00 | Using where |
+----+-------------+-------+------------+--------+---------------+---------+---------+------+------+----------+-------------+
2 rows in set (0.05 sec)

9. rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数。

10. filtered

5.7 版本之前,使用 explain extended 命令才会出现此字段,5.7 版本之后默认有此字段,这个字段表示存储引擎返回的数据在 server 层过滤后,剩下多少满足查询的记录数量的比例,这里是百分比,并非具体记录数。在单表查询中这一列没有什么意义,更应该在连接查询中关注驱动表对应的执行计划的 filtered 值。

11. extra 列

  1. No table used : 不带 from字句的查询或者 from dual 查询, 如下:

    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 |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    1 row in set (0.05 sec)
    
  2. Impossible WHERE : 查询语句的 where 字句永远为 false,如下:

    mysql> explain select MAX(id) from t1 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 |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    1 row in set (0.05 sec)
    
  3. No matching min/max row : 当查询列表有 MIN 或者 MAX 函数,但是并没有记录符合 WHERE 字句中的搜索条件,如下:

    mysql> explain select MAX(id) from t1 where a = 'asda';
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------------------+
    | 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 |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------------------+
    1 row in set (0.04 sec)
    
  4. Using index : 不需要回表,直接通过索引查询 (覆盖索引) ,如下:

    mysql> explain select a from t1;
    +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | t1    | NULL       | index | NULL          | idx_a | 5       | NULL |  100 |   100.00 | Using index |
    +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
    1 row in set (0.05 sec)
    
  5. Using index condition : 有些搜索条件中虽然出现了索引列,但是不能充当边界条件来形成扫描区间,即不能用来减少需要扫描的记录数量,将会提示该额外信息; 出现了该信息提示,也可以说明该查询使用了索引下推 (Index Condition Pushdown ICP)。如下:

    mysql> explain select * from t1 where a > '1';
    +----+-------------+----------+------------+-------+---------------+-----+---------+------+------+----------+-----------------------+
    | id | select_type | table    | partitions | type  | possible_keys | key | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+----------+------------+-------+---------------+-----+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | t1		  | NULL       | range | a             | a   | 5       | NULL |    1 |   100.00 | Using index condition |
    +----+-------------+----------+------------+-------+---------------+-----+---------+------+------+----------+-----------------------+
    1 row in set (0.05 sec)
    
  6. Using where : 当某个查询条件需要在 server 层进行判断时,会提示该信息。如下:这里虽然 a 列是索引,但是MySQL 判断全表扫描效率更高,因此没有使用索引(key 为 null);全表扫描后将数据返回给 Server 层,在 Server 层过滤 a > ‘a’ 并且 c = ‘c’ 的条件.

    mysql> explain select * from t1 where a > 'a' and c = 'c';
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | idx_a         | NULL | NULL    | NULL |  100 |    10.00 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set (0.04 sec)
    
  7. Using join buffer(Block Nested Loop) : 在连接查询的执行过程中,当被驱动表不能有效利用索引加快访问速度时,MySQL一般会为其分配一块名为连接缓冲区(Join Buffer)的内存卡来加快查询速度;也就是使用基于块的嵌套循环算法来执行连接查询。如下:

    sql> explain select * from t1 inner join t2 on t1.c = t2.c;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL                                               |
    |  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
    2 rows in set (0.06 sec)
    

    在 Extra 中显示了两个提示:

    • Using join buffer (Block Nested Loop) : 因为对于 t2 表的访问不能有效利用索引,所以只能退而求其次,使用 Join Buffer 来减少 t2 表的访问次数,提高性能。
    • Using where : 查询语句中的 t2 on t1.c = t2.c条件,因此 t1 表是驱动表,t2 表是被驱动表,所以在访问 t2 表的时候 t1.c 的值已经确定下来了,所以提示了 Using where 信息。
  8. Using intersect(…)、Using union(…)、Using sort_union(…) : 说明查询使用了 Intersect、Union 或 Sort-Union 索引合并。

  9. Zero limit :当 Limit 参数为0时,表示不打算从表中读取出任何记录。

    mysql> explain select * from t1 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 |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------+
    1 row in set (0.04 sec)
    
  10. Using filesort : 在有些情况下, 对结果集总的记录进行排序时,是可以使用到索引的。但是很多情况下排序操作是无法使用到索引的,只能在内存或磁盘中进行排序,而这种在内存或磁盘中进行排序的方式称为文件排序(filesort)。如果某个查询使用到了文件排序,则会显示该提示,如下:

    mysql> explain select * from t1 order by c;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | Using filesort |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    1 row in set (0.04 sec)
    
  11. Using temporary :在许多查询的执行过程中,MySQL会借助临时表来完成一些去重、排序等功能。如果不能有效利用索引来完成查询,MySQL很有可能通过建立内部临时表执行查询。如果查询中使用了内部临时表则会展示该信息。

    mysql> explain select distinct(c) from t1;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra           |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | Using temporary |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    1 row in set (0.04 sec)
    
  12. Loosescan : 在将 IN 子查询转为半连接时,如果采用的是 LooseScan 执行策略,则驱动表的 Extra 列会显示该提示。

  13. Firstmatch :在将 IN 子查询转为半连接时,如果采用的是 FirstMatch 执行策略,则被驱动表的 Extra 列会显示该提示。

  14. NULL : 查询的列未被索引覆盖,并且where 筛选条件是索引的前导列,意味着用到了索引,但是部分字段未被索引覆盖,必须通过 “回表” 来实现,不是纯粹地用到了索引,也不是完全没用到索引。(需要回表操作完成的查询),如下:

    mysql> explain select * from t1 where a = 'a';
    +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | t1    | NULL       | ref  | idx_a         | idx_a | 5       | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
    

四、补充

1. EXPLAIN 扩展

1.1 Extend EXPLAIN

在使用 Explain 语句查看 SQL 的执行计划后,紧接着还可以使用 show warnings 语句来查看与这个查询计划有关的扩展信息, 如下:

mysql> explain select * from t1 where a = 'a' and c = 'c' ;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_a         | idx_a | 5       | const |    1 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
1 row in set (0.04 sec)mysql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                          |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `demo`.`t1`.`id` AS `id`,`demo`.`t1`.`a` AS `a`,`demo`.`t1`.`b` AS `b`,`demo`.`t1`.`c` AS `c` from `demo`.`t1` where ((`demo`.`t1`.`a` = 'a') and (`demo`.`t1`.`c` = 'c')) |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)

show warnings 展示了三个字段:Level、Code、 Message。其中当 Code =1003 时,Message 字段展示的信息类似于查询优化器将查询语句重写后的语句。

1.2 JSON 格式的执行计划

可以在 Explain 命令后追加 format=json 命令以得到一个 JSON 格式的执行计划,里面除了常规的信息外海包含该计划花费的成本。

mysql> explain format=json select * from t1 where a = 'a' and c = 'c';
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| {"query_block": {"select_id": 1, # 整个查询语句只有一个 Select 关键字,该关键字对应的id号为 1"cost_info": {"query_cost": "1.20" # 整个查询语句预计执行成本},"table": {"table_name": "t1","access_type": "ref","possible_keys": ["idx_a"],"key": "idx_a","used_key_parts": ["a"],"key_length": "5","ref": ["const"],"rows_examined_per_scan": 1,"rows_produced_per_join": 0,"filtered": "10.00","cost_info": {"read_cost": "1.00","eval_cost": "0.02","prefix_cost": "1.20","data_read_per_join": "78"},# 执行查询中设计的列"used_columns": [  "id","a","b","c"],# 针对单表的查询条件"attached_condition": "(`demo`.`t1`.`c` = 'c')"}}
} |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.07 sec)

2. Intersection、Union、Sort-Union 索引合并

一般情况下 MySQL 只会为单个索引生成扫描空间,但是特殊情况下MySQL 也会为多个索引生成扫描区间。

2.1 Intersection

select  from  t1 where k1='a' and k2 = 'b'

其中 k1,k2 都是索引,此处存在三种方案:

  1. 扫描 k1 辅助索引,回表查询 k2 = ‘b’ 的记录
  2. 扫描 k2 辅助索引,回表查询 k1 = 'a’的记录
  3. 同时扫描 k1 和 k2 的辅助索引,然后从二者的操作结果中找出 id 列值相同的记录。此方案,即为 Intersection (交集)索引合并

如果要使用 Intersection (交集)索引合并 ,并且每个使用到的索引都是二级索引,那么则要求从每个索引中获取到的二级索引记录都是按照主键值排序的。比如上面的sql, 查询 k1 = ‘a’ 的辅助索引记录,因为其辅助索引列都是 a,又因为在索引列相同时按照主键排序,所以这里查询出来的二级索引记录是按照主键排序的,k2 = ‘b’ 同理,因此 上述SQL 可以使用 Intersection索引合并。


为什么需要主键有序?

  1. 从两个有序集合中取交集更为容易
  2. 如果获取到的id 值是有序的,则回表时就不是随机 IO了(相当于实现了MMR),从而提高效率。

2.2 Union

基本与 Intersection (交集)索引合并 类似,只不过这里取的是并集。

如:select from t1 where k1='a' or k2 = 'b'

2.3 Sort-Union

Union 需要保证扫描出来的二级索引的主键值是有序的,而 Sort-Union 则会将从各个索引中扫描到的记录的主键值进行排序,在按照 Union 索引合并的方式执行查询。相较于 Union, Sort-Union 多了一步将查询出来的记录按照主键排序的过程。

需要注意的是并不存在 Sort-Intersection 索引合并,在《MySQL是怎样运行的》一书中说是 Sort-Intersection 索引合并 查询出的记录太多,导致回表代价太大。

3. 基于块的嵌套循环连接

在连接查询过程中,分为驱动表和被驱动表,驱动表只需要访问一次,被驱动表可能需要访问多次。两表连接查询的过程并不是将所有满足条件的驱动表记录先查询出来放到一个地方然后再去被驱动表中查询(因为可能驱动表记录太大,无法存储),而是每获取到一条驱动表记录,就立即到被驱动表中寻找匹配的记录,如果得到了匹配的记录,就把组合后的记录发送给客户端,然后再到驱动表中获取下一条记录,这个过程将重复进行。但上述这种方案需要频繁访问 被驱动表,因此InnoDB 存在一个 Join Buffer (连接缓冲区) 的概念,Join Buffer 在执行连接查询前申请的一块大小固定的内存。先把若干条驱动表结果集中的记录记录在 Join Buffer 中,然后开始扫描被驱动表,每一条被动表的记录一次性与 Join Buffer 中的多条驱动表记录进行匹配。由于匹配的过程是在内存中完成的,因此可以显著减少被驱动表的 IO 代价。此为基于块的嵌套循环连接(Block Nested-Loop Join)算法。

需要注意的是 : Join Buffer 中并不会存放驱动表记录的所有列,只有查询列表中的列和过滤条件中的列才会被放到 Join Buffer 中。

Join Buffer 示意图
在这里插入图片描述


五、参考内容

书籍:《MySQL是怎样运行的——从根儿上理解MySQL》
全技术栈企业级性能调优万花筒
一张图彻底搞懂MySQL的 explain
MySQL优化—工欲善其事,必先利其器之EXPLAIN*
https://coding.imooc.com/class/442.html
https://segmentfault.com/a/1190000021458117?utm_source=tag-newest
https://www.cnblogs.com/tufujie/p/9413852.html
https://blog.csdn.net/qq_27399407/article/details/93741225
https://smartan123.github.io/book/?file=home-%E9%A6%96%E9%A1%B5
https://www.cnblogs.com/zhanjindong/archive/2013/11/23/3439042.html
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/12458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用C++实时读取串口数据(window使用已编译LibModbus库并用QT实现一个实时读取串口数据)

先看这篇文章&#xff0c;写得很详细: QT应用篇 四、window编译LibModbus库并用QT编写一个Modbus主机 手把手教学 编译好的LibModbus库可以在上面文章里下载&#xff0c;也可以在我的链接里下载&#xff1a; 为了在Qt Creator中创建新项目并嵌入上述C代码&#xff0c;请执行以…

C++进阶:红黑树介绍及模拟实现(图示详解过程)

C进阶&#xff1a;红黑树介绍及模拟实现 上次介绍了AVL树&#xff1a;C进阶&#xff1a;AVL树详解及模拟实现&#xff08;图示讲解旋转过程&#xff09; 今天就来紧接着来红黑树啦!!! 文章目录 1.红黑树介绍约束规则 2.项目文件规划3.整体框架&#xff08;节点和Tree&#xf…

whisper报错:hp, ht, pid, tid = _winapi.CreateProcess [WinError 2] 系统找不到指定的文件。

in _execute_child hp&#xff0c; ht&#xff0c; pid&#xff0c; tid _winapi.CreateProcess&#xff08;executable&#xff0c; args&#xff0c; FileNotFoundError&#xff1a; [WinError 2] 系统找不到指定的文件。 原因&#xff1a; 没装ffmpeg 或者 ffmpeg没添加到…

k8s pod就绪探针

Pod 可能需要时间来加载配置或数据&#xff0c;或者可能需要执行预热过程以防止第一个用户请求时间太长影响了用户体验。在这种情况下&#xff0c;不希望该 pod 立即开始接收请求&#xff0c;尤其是在运行的实例可以正确快速地处理请求的情况下。不要将请求转发到正在启动的 po…

YOLOv5独家改进:backbone改进 | 微软新作StarNet:超强轻量级Backbone | CVPR 2024

💡💡💡创新点:star operation(元素乘法)在无需加宽网络下,将输入映射到高维非线性特征空间的能力,这就是StarNet的核心创新,在紧凑的网络结构和较低的能耗下展示了令人印象深刻的性能和低延迟 💡💡💡如何跟YOLOv5结合:替代YOLOv5的backbone 收录 YOL…

电容笔记汇总

电容 一、电容理论基础 1、电容的本质 两个相互靠近的导体&#xff0c;中间夹一层不导电的绝缘介质&#xff0c;这就构成了电容器。当电容器的两个极板之间加上电压时&#xff0c;电容器就会储存电荷。 两个相互靠近的金属板中间夹一层绝缘介质组成的器件&#xff0c;当两端…

豆浆机缺水检测功能如何实现的

豆浆机缺水检测功能的实现是通过光学液位传感器来完成的。这种传感器具有多种优势&#xff0c;如内部所有元器件经过树脂胶封处理&#xff0c;没有任何机械活动部件&#xff0c;免调试、免检验、免维护等特点。它采用了光电液位传感器内置的光学电子元件&#xff0c;体积小、功…

Docker常用镜像安装

1. mysql 1.1 安装 获取镜像 docker pull mysql:8.0.30创建文件挂载目录 创建容器并运行 docker run -p 3306:3306 --name mysql8 \ -v /home/docker/mysql8/log:/var/log/mysql \ -v /home/docker/mysql8/data:/var/lib/mysql \ -v /home/docker/mysql8/mysql-files:/va…

保研机试之【设备驱动程序】

B选项&#xff1a; 综上&#xff0c;我认为这道题选择D~

一些近来对内网攻防的思考

我知道我最近托更托了很久了&#xff0c;其实也不是小编懒啊 这小编也是一直在写&#xff0c;但是遇到的问题比较多&#xff08;我太菜了&#xff09;&#xff0c;所以一直拖着。 但是总不能不更吧 那就讲一下进来的一些内网攻防的思考吧 1.CrossC2上线Linux到CS(成功) …

用友网络的危与机:2023年亏损约10亿元,王文京面临严肃拷问

“企业在新的产业浪潮来临时&#xff0c;应该主动推进新阶段的产品和业务创新&#xff0c;这样才能够在新的浪潮成为主流的时候&#xff0c;走到行业前面&#xff0c;否则就会从产业发展的潮流中掉下来”。用友网络创始人王文京&#xff0c;曾用“冲浪理论”形容一家企业成功的…

Steam喜加一,限时免费领取《Machinika Museum》

《Machinika Museum》限时免费领取啦&#xff01;这是一款烧脑解谜游戏&#xff0c;让你挖掘神秘外星装置的秘密。在这个非常特别的异星装置博物馆里&#xff0c;你将扮演一名研究员&#xff0c;负责解开各种机械谜题&#xff0c;探索背后的故事。 在这个未来世界&#xff0c;外…

MLP的代替:KAN

受柯尔莫哥洛夫-阿诺德表示定理的启发&#xff0c;作者提出柯尔莫哥洛夫-阿诺德网络&#xff08;KAN&#xff09;作为多层感知器&#xff08;MLP&#xff09;有前途的替代品。MLP 在节点&#xff08;“神经元”&#xff09;上具有固定的激活函数&#xff0c;而 KAN 在边&#x…

自动化中遇到的问题归纳总结

1、动态元素定位不到 解决方法&#xff1a;尽量使用固定元素定位&#xff0c;如没有固定元素&#xff0c;则采用绝对路径进行定位&#xff0c;因为元素路径是唯一且不变的 2、自动化脚本执行速度较慢 尽量使用css方法定位元素&#xff0c;使用等待时&#xff0c;少用sleep方…

[笔记] srlua库编译

文章目录 前言一、环境二、编译过程2.1 gcc安装2.2 编译lua2.3 编译srlua库 三、测试srlua库参考总结 前言 一、环境 centos7.9 gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) lua5.1源码 srlua 源码 二、编译过程 2.1 gcc安装 yum install gcc这里gcc安装过程和环…

苹果电脑卡顿反应慢怎么办 苹果电脑卡顿严重解决方法 mac电脑太卡了怎么办

作为Mac用户&#xff0c;你是否正在经历或者曾经遭遇过电脑卡顿、反应慢的困扰&#xff1f;这可能是由于多种原因导致的&#xff0c;包括自启动程序过多、系统与应用未及时更新、内存管理不当等。今天和你一起来探讨下&#xff0c;苹果电脑卡顿反应慢时怎么办。希望能够帮助你解…

KBPC5010-ASEMI电源控制柜专用KBPC5010

编辑&#xff1a;ll KBPC5010-ASEMI电源控制柜专用KBPC5010 型号&#xff1a;KBPC5010 品牌&#xff1a;ASEMI 封装&#xff1a;KBPC-4 正向电流&#xff08;Id&#xff09;&#xff1a;50A 反向耐压&#xff08;VRRM&#xff09;&#xff1a;1000V 正向浪涌电流&#x…

FreeRTOS中的动态内存管理(heap_1、heap_2、heap_3、heap_4)

FreeRTOS 提供了多种动态内存分配方案&#xff0c;这些方案通过不同的内存管理器&#xff08;heap managers&#xff09;实现&#xff0c;主要位于 FreeRTOS/Source/portable/MemMang 目录下。以下是几种常见的动态内存分配方案&#xff1a; heap_1 特点&#xff1a; 简单性…

大数据可视化实验(四):Excel数据可视化

目录 一、实验目的... 1 二、实验环境... 1 三、实验内容... 1 1&#xff09;excel函数应用.. 1 2&#xff09;数据透视图绘制... 3 四、总结与心得体会... 5 一、实验目的 1&#xff09;掌握函数和公式的原理 2&#xff09;掌握在单元格或编辑栏中直接输入带函数的公式…

kafka学习笔记04(小滴课堂)

Kafka的producer生产者发送到Broker分区策略讲解 Kafka核心API模块-producer API讲解实战 代码&#xff1a; ProducerRecord介绍和key的作用 Kafka核心API模块-producerAPI回调函数实战 producer生产者发送指定分区实战 我们设置5个分区。 我们指定分区。 重新指定一个分区&am…