1. 前言

本文主要分为三个部分,第一个部分讲解 MySQL 查询时会经过哪些过程;第二个部分讲解 Explain 的基本用法;第三个部分讲解 Explain 参数分析。

如下很多操作只是一些基本的操作,如果相对 MySQL 进行深层次的学习还是要对 B+ 树有一定了解

准备工作

2. MySQL 查询过程

服务器程序处理来自客户端的查询请求大致需要经过三个部分 : 连接管理、解析与优化、存储引擎

image2022-12-28_16-1-44.png
MySQL 查询过程如下:

  1. 客户端将查询发送到 MySQL 服务器
  2. 服务器先检查查询缓存,如果命中,立即返回缓存中的结果;否则进入下一阶段
  3. 服务器对 SQL 进行解析预处理,再由优化器生成对象的执行计划
  4. MySQL 根据优化器生成的执行计划,调用存储引擎API来执行查询
  5. 服务器将结果返回给客户端,同时缓存查询结果

image2022-12-27_15-52-41.png

3. Explain 基本介绍

3.1. Explain 是什么?

  • MySQL 的 Explain 是一个用于查看 SELECT、INSERT、UPDATE、DELETE 语句 执行计划 的命令。
  • 执行计划:指 SQL 语句在经过 MySQL 查询优化器 的优化会后,具体的执行方式。

3.2. Explain 解决了那些问题?

  • 它可以帮助我们了解查询执行计划,并识别潜在的性能问题

3.3. Explain 如何使用?

  • 语法
1
2
3
4
# 第一条:SQL基础执行信息
EXPLAIN EXTENDED SELECT * FROM actor;
# MySQL优化后的查询语句,可以看到优化器具体优化的SQL信息
SHOW WARNINGS;
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> explain extended SELECT * FROM actor;show warnings;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | actor | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set (0.03 sec)

+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1681 | 'EXTENDED' is deprecated and will be removed in a future release. |
| Note | 1003 | /* select#1 */ select `explain_demo_test`.`actor`.`id` AS `id`,`explain_demo_test`.`actor`.`name` AS `name`,`explain_demo_test`.`actor`.`update_time` AS `update_time` from `explain_demo_test`.`actor` |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.03 sec)

4. Explain 参数分析

  • 每张表对应的索引情况,使用Explain得到的结果都不一样,执行 Explain 请对照索引情况看

select 的序列号,表示优化器访问表的顺序。

  • 序号越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行

查询数据的操作类型(简单、复杂),表示使用的 SELECT 语句的类型

  • simple:简单查询,单表查询
  • primary:复杂查询中最外层的 select
  • subquery:包含在 select 中的子查询(不在 from 子句中)
  • derived:包含在 from 子句中的子查询,也称为派生表(derived)

查询数据的操作类型(简单、复杂),表示使用的 SELECT 语句的类型

  • 表示被访问的表的名称

匹配的分区信息,不用管

表示访问表的关联类型或者连接类型。一些常见的连接类型包括:

  • system:正在访问只有一行的表。
  • const:使用具有唯一或主键约束的索引列访问表。
  • eq_ref:使用唯一索引或主键访问表。
  • ref:使用非唯一索引访问表。
  • range : 使用一个索引来检索给定范围的行, 通常出现在 in(), between ,> ,<, >= 等操作中
  • index:只遍历索引树。
  • ALL:全表扫描,性能最拉垮。

优先级排序:system > const > eq_ref > ref > range > index > ALL

通常优化至少到 range 级别,最好能优化到ref

查询数据可能使用哪些索引来查找

  • 如果该列是NULL,则没有使用索引

显示 MySQL 实际使用的索引

  • 如果为 NULL,则没有使用索引查询

表示索引中使用的字节数,通过该列计算查询中使用的索引的长度

表的索引字段关联了哪张表的哪个字段或常量

  • const(常量)
  • 字段名(例:film.id)

估算出找到所需的记录或所需读取的行数

  • 数值越小越好

结果的行数占读取行数的百分比

  • 值越大越好

十分重要的额外信息,如下是常见的值

  • Using index:使用覆盖索引。
  • Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖。
  • Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围;
  • Using temporary:mysql需要创建一张临时表来处理查询。
  • Using filesort:外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。
  • Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段

4.1. id

id:select 查询序列号

  • 序号越大执行优先级越高;
  • id相同,执行顺序由上至下;
  • id不同,id值越大优先级越高,越先被执行;
  • id为NULL最后执行;

4.2. select_type

select_type:select_type 表示对应行是简单还是复杂的查询

  • SIMPLE : 简单查询,一般指单表查询。
  • DERIVED :派生表,包含在 from 子句中的子查询。
  • PRIMARY :复杂查询,一般指最外层的 select。
  • SUBQUERY :包含在 select 中的子查询(不在 from 子句中) 。
  • UNION : 表示使用 UNION 运算符的 SELECT 语句。
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#关闭mysql5.7新特性对衍生表的合并优化
mysql> set session optimizer_switch='derived_merge=off';
Query OK, 0 rows affected (0.39 sec)

# Explain 执行的过程,其中对应的 select_type
# EXPLAIN SELECT ==> id = 1; select_type = PRIMARY; table = <derived3>
# ( SELECT 1 FROM actor WHERE id = 1 ) ==> id = 2; select_type = SUBQUERY; table = actor
# FROM
# ( SELECT * FROM film WHERE id = 1 ) der; ==> id = 3; select_type = DERIVED; table = film

mysql> explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | <derived3> | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 3 | DERIVED | film | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | actor | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
3 rows in set (0.03 sec)

# 还原默认配置;
mysql> set session optimizer_switch='derived_merge=on';
Query OK, 0 rows affected (0.01 sec)

# select_type = SIMPLE ,说明使用的是单表
mysql> explain select * from film where id = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | film | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set (0.03 sec)

# select_type = UNION ,说明在 union 中的第二个和随后的 select
mysql> explain select 1 union all select 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
| 2 | UNION | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
2 rows in set (0.03 sec)

4.3. table

table:这一列表示 explain 的一行正在访问哪个表。

  • 当 from 子句中有子查询时,table 列是 格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询。

衍生表

  • 执行这行如果想要得到衍生表,需要执行以下两条语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
#关闭mysql5.7新特性对衍生表的合并优化
mysql> set session optimizer_switch='derived_merge=off';
Query OK, 0 rows affected (0.39 sec)

# 得到的 table 列中就存在衍生表信息。
mysql> explain select * from (select * from film where id = 1) der;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 2 | DERIVED | film | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
2 rows in set (0.06 sec)

4.4. type

type:这一列表示关联类型访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。

依次从最优到最差分别为,一般来说,得保证查询达到range级别,最好达到ref:

  1. null
  2. system
  3. const
  4. eq_ref
  5. ref
  6. range
  7. index
  8. ALL
  1. NULL

    mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。

    1
    2
    3
    4
    5
    6
    7
    mysql> explain select min(id) from film;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    | 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 | Select tables optimized away |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    1 row in set (0.05 sec)
  2. const, system

    mysql能对查询的某部分进行优化并将其转化成一个常量,所以表最多有一个匹配行,读取1次,速度比较快。

    system是const的特例,表里只有一条元组匹配时为system

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    mysql> explain extended select * from (select * from film where id = 1) tmp;show warnings;
    +----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
    | 1 | PRIMARY | <derived2> | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
    | 2 | DERIVED | film | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
    +----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
    2 rows in set (0.10 sec)

    +---------+------+-------------------------------------------------------------------+
    | Level | Code | Message |
    +---------+------+-------------------------------------------------------------------+
    | Warning | 1681 | 'EXTENDED' is deprecated and will be removed in a future release. |
    | Note | 1003 | /* select#1 */ select '1' AS `id`,'film1' AS `name` from dual |
    +---------+------+-------------------------------------------------------------------+
    2 rows in set (0.05 sec)
  3. eq_ref

    primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。

    这可能是在const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 其中 film表 使用的是主键索引, film_actor使用的是联合索引 NDEX `idx_film_actor_id`(`film_id`, `actor_id`) USING BTREE
    mysql> explain select * from film_actor left join film on film_actor.film_id = film.id;
    +----+-------------+------------+------------+--------+---------------+---------+---------+--------------------------------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+------------+------------+--------+---------------+---------+---------+--------------------------------------+------+----------+-------+
    | 1 | SIMPLE | film_actor | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL |
    | 1 | SIMPLE | film | NULL | eq_ref | PRIMARY | PRIMARY | 4 | explain_demo_test.film_actor.film_id | 1 | 100.00 | NULL |
    +----+-------------+------------+------------+--------+---------------+---------+---------+--------------------------------------+------+----------+-------+
    2 rows in set (0.02 sec)
  4. ref

    相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。

    1. 普通索引

    • file 表的信息

      1
      2
      3
      4
      5
      6
      7
      file表的索引信息
      - 主键索引:PRIMARY KEY (`id`)
      - 联合索引:INDEX `idx_name`(`name`)

      file表的字段类型
      - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID'
      - `name` varchar(10) DEFAULT NULL COMMENT '电影名称'
    • Explain 分析结果

      1
      2
      3
      4
      5
      6
      7
      mysql> explain select * from film where name = 'film1';
      +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
      | 1 | SIMPLE | film | NULL | ref | idx_name | idx_name | 33 | const | 1 | 100.00 | Using index |
      +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
      1 row in set (0.68 sec)

    2. 唯一性索引的部分前缀

    • 表的信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      file 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)
      - 联合索引:KEY `idx_name`(`name`)

      file 表的字段类型
      - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID'
      - `name` varchar(10) DEFAULT NULL COMMENT '电影名称'

      film_actor 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)
      - 联合索引:KEY `idx_film_actor_id` (`film_id`,`actor_id`)

      film_actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `film_id` int(11) NOT NULL COMMENT '电影ID'
      - `actor_id` int(11) NOT NULL COMMENT '演员ID'
      - `remark` varchar(255) DEFAULT NULL COMMENT '备注'
    • Explain 分析结果

      1
      2
      3
      4
      5
      6
      7
      8
      mysql> explain select film_id from film left join film_actor on film.id = film_actor.film_id;
      +----+-------------+------------+------------+-------+-------------------+-------------------+---------+---------------------------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+------------+------------+-------+-------------------+-------------------+---------+---------------------------+------+----------+-------------+
      | 1 | SIMPLE | film | NULL | index | NULL | idx_name | 33 | NULL | 3 | 100.00 | Using index |
      | 1 | SIMPLE | film_actor | NULL | ref | idx_film_actor_id | idx_film_actor_id | 4 | explain_demo_test.film.id | 1 | 100.00 | Using index |
      +----+-------------+------------+------------+-------+-------------------+-------------------+---------+---------------------------+------+----------+-------------+
      2 rows in set (0.03 sec)
  5. range

    范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。

    1
    2
    3
    4
    5
    6
    7
    mysql> explain select * from actor where id > 1;
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    | 1 | SIMPLE | actor | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    1 row in set (0.04 sec)
  6. index

    扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些。

    1
    2
    3
    4
    5
    6
    7
    8
    mysql> explain select * from film;

    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    | 1 | SIMPLE | film | NULL | index | NULL | idx_name | 33 | NULL | 3 | 100.00 | Using index |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    1 row in set (0.03 sec)
  7. ALL

    即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。

    1
    2
    3
    4
    5
    6
    7
    8
    mysql> explain select * from actor;

    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    | 1 | SIMPLE | actor | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
    1 row in set (0.04 sec)

4.5. possible_keys

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

如果该列是NULL,则没有相关的索引。

注意

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

4.6. key

key列表示实际用到的索引有哪些,如果没有使用索引,则该列是 NULL

4.7. key_len

key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度.

  • 表的信息
1
2
3
4
5
6
7
8
9
10
11
film_actor 表的索引信息

- 主键索引:PRIMARY KEY (`id`)
- 联合索引:KEY `idx_film_actor_id` (`film_id`,`actor_id`)

**film_actor 表的字段类型**

- `id` int(11) NOT NULL COMMENT '主键ID'
- `film_id` int(11) NOT NULL COMMENT '电影ID'
- `actor_id` int(11) NOT NULL COMMENT '演员ID'
- `remark` varchar(255) DEFAULT NULL COMMENT '备注'

key_len 为什么为4?

因为走到了索引 idx_film_actor_id ,而 字段(film_id)数据类型是int , int就是占四个字节

1
2
3
4
5
6
7
mysql> explain select * from film_actor where film_id = 2;
+----+-------------+------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | film_actor | NULL | ref | idx_film_actor_id | idx_film_actor_id | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------+
1 row in set (1.83 sec)

key_len 计算规则如下

字符串

  • 5.0.3以后版本中,n均代表字符数,而不是字节数,如果是utf-8,一个数字或字母占1个字节,一个汉字占3个字节
  • char(n):如果存汉字长度就是 3n 字节
  • varchar(n):如果存汉字则长度是 3n + 2 字节,加的2字节用来存储字符串长度,因为varchar是变长字符串
  • 例如:某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节

数值类型

  • tinyint:1字节
  • smallint:2字节
  • int:4字节
  • bigint:8字节

时间类型

  • date:3字节
  • timestamp:4字节
  • datetime:8字节

NULL

  • 如果字段允许为 NULL,需要1字节记录是否为 NULL

4.8. ref

这一列显示了在key列记录的索引中,表查找值所用到的列或常量

常见的有:const(常量),字段名(例:film.id)

4.9. rows

这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。

4.10. filtered

对于单表而言这个列没啥意义,结果函数占总数的百分比。

4.11. Extra

这一列展示的是额外信息。常见的重要值如下:

  1. Using index(覆盖索引)
  2. Using where(查询的列未被索引覆盖)
  3. Using index condition(不完全被索引覆盖)
  4. Using temporary
  5. Using filesort
  6. Select tables optimized away
  1. Using index(覆盖索引)

    mysql执行计划explain结果里的key有使用索引,如果 select 后面查询的字段都可以从这个索引的树中获取,这种情况可以说是用到了覆盖索引,extra里一般都有using index;

    • 表信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      film_actor 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)
      - 联合索引:KEY `idx_film_actor_id` (`film_id`,`actor_id`)

      film_actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `film_id` int(11) NOT NULL COMMENT '电影ID'
      - `actor_id` int(11) NOT NULL COMMENT '演员ID'
      - `remark` varchar(255) DEFAULT NULL COMMENT '备注'
    • 示例

      1
      2
      3
      4
      5
      6
      7
      mysql> explain select film_id from film_actor where film_id = 1;
      +----+-------------+------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+
      | 1 | SIMPLE | film_actor | NULL | ref | idx_film_actor_id | idx_film_actor_id | 4 | const | 2 | 100.00 | Using index |
      +----+-------------+------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+
      1 row in set (19.56 sec)
  2. Using where(查询的列未被索引覆盖)

    使用 where 语句来处理结果,并且查询的列未被索引覆盖

    • 表信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      actor 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)

      actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `name` varchar(45) DEFAULT NULL COMMENT '演员名称'
      - `update_time` datetime DEFAULT NULL COMMENT '更新时间'
      - 主键索引:PRIMARY KEY (`id`)

      actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `name` varchar(45) DEFAULT NULL COMMENT '演员名称'
      - `update_time` datetime DEFAULT NULL COMMENT '更新时间'
    • 示例

      1
      2
      3
      4
      5
      6
      7
      mysql> explain select * from actor where name = 'a';
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
      | 1 | SIMPLE | actor | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
      1 row in set (0.03 sec)
  3. Using index condition(不完全被索引覆盖)

    查询的列不完全被索引覆盖,where条件中是一个前导列的范围;

    • 表信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      film_actor 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)
      - 联合索引:KEY `idx_film_actor_id` (`film_id`,`actor_id`)

      film_actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `film_id` int(11) NOT NULL COMMENT '电影ID'
      - `actor_id` int(11) NOT NULL COMMENT '演员ID'
      - `remark` varchar(255) DEFAULT NULL COMMENT '备注'
    • 示例

      1
      2
      3
      4
      5
      6
      7
      mysql> explain select * from film_actor where film_id > 1;
      +----+-------------+------------+------------+-------+-------------------+-------------------+---------+------+------+----------+-----------------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+------------+------------+-------+-------------------+-------------------+---------+------+------+----------+-----------------------+
      | 1 | SIMPLE | film_actor | NULL | range | idx_film_actor_id | idx_film_actor_id | 4 | NULL | 1 | 100.00 | Using index condition |
      +----+-------------+------------+------------+-------+-------------------+-------------------+---------+------+------+----------+-----------------------+
      1 row in set (0.03 sec)
  4. Using temporary

    mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。

    • 表信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      actor 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)

      actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `name` varchar(45) DEFAULT NULL COMMENT '演员名称'
      - `update_time` datetime DEFAULT NULL COMMENT '更新时间'

      actor.name 没有索引,此时创建了张临时表来distinct
    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      mysql> explain select distinct name from actor;

      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
      | 1 | SIMPLE | actor | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using temporary |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
      1 row in set (0.03 sec)
    • film.name 建立了idx_name索引,此时查询时extra是using index,没有用临时表

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # 新增 idx_name 索引
      mysql> ALTER TABLE `film` ADD INDEX `idx_name`(`name`) USING BTREE;
      Query OK, 0 rows affected (0.09 sec)
      Records: 0 Duplicates: 0 Warnings: 0

      mysql> explain select distinct name from film;
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
      | 1 | SIMPLE | film | NULL | index | idx_name | idx_name | 33 | NULL | 3 | 100.00 | Using index |
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
      1 row in set (0.04 sec)

      # 删除 idx_name 索引
      mysql> ALTER TABLE `film` DROP INDEX `idx_name`;
      Query OK, 0 rows affected (1.78 sec)
      Records: 0 Duplicates: 0 Warnings: 0
  5. Using filesort

    将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的。

    • 表信息

      1
      2
      3
      4
      5
      6
      7
      actor 表的索引信息
      - 主键索引:PRIMARY KEY (`id`)

      actor 表的字段类型
      - `id` int(11) NOT NULL COMMENT '主键ID'
      - `name` varchar(45) DEFAULT NULL COMMENT '演员名称'
      - `update_time` datetime DEFAULT NULL COMMENT '更新时间'
    • actor.name未创建索引,会浏览actor整个表,保存排序关键字name和对应的id,然后排序name并检索行记录

      1
      2
      3
      4
      5
      6
      7
      mysql> explain select * from actor order by name;
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
      | 1 | SIMPLE | actor | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using filesort |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
      1 row in set (20.78 sec)
    • film.name建立了idx_name索引,此时查询时extra是using index

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      # 新增 idx_name 索引
      mysql> ALTER TABLE `film` ADD INDEX `idx_name`(`name`) USING BTREE;
      Query OK, 0 rows affected (0.09 sec)
      Records: 0 Duplicates: 0 Warnings: 0

      # Extra:Using index。添加idx_name 索引之后使用到了覆盖索引
      mysql> explain select * from film order by name;
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
      | 1 | SIMPLE | film | NULL | index | NULL | idx_name | 33 | NULL | 3 | 100.00 | Using index |
      +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
      1 row in set (0.20 sec)

      # 删除 idx_name 索引
      mysql> ALTER TABLE `film` DROP INDEX `idx_name`;
      Query OK, 0 rows affected (1.78 sec)
      Records: 0 Duplicates: 0 Warnings: 0
  6. Select tables optimized away

    使用某些聚合函数(比如 max、min)来访问存在索引的某个字段

    • 示例
    1
    2
    3
    4
    5
    6
    7
    mysql> explain select min(id) from film;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    | 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 | Select tables optimized away |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    1 row in set (0.11 sec)