MySQL · 执行计划

简述

在 MySQL 中,当数据量增长的特别大的时候就需要用到索引来优化 SQL 语句,使用 Explain 来查看 SQL 语句的执行计划,是优化 SQL 最有效地的一种手段。

使用方法

语法explain + SQL

示例explain select * from customer

1
2
3
4
5
6
7
mysql> explain select * from customer;
+----+-------------+----------+------+---------------+------+---------+------+--------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows |
+----+-------------+----------+------+---------------+------+---------+------+--------+
| 1 | SIMPLE | customer | ALL | NULL | NULL | NULL | NULL | 936161 |
+----+-------------+----------+------+---------------+------+---------+------+--------+
1 row in set

参数介绍

id

含义:SELECT 标识符,SQL 执行顺序的标识。

三种情况

  • id 相同,执行顺序由上至下。

    1
    2
    3
    4
    5
    6
    7
    8
    mysql> explain select t2.* from t1,t2,t3 where t1.id = t2.id and t1.id = t3.id and t1.other_column = '';
    +----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------------+
    | 1 | SIMPLE | t1 | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where |
    | 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | |
    | 1 | SIMPLE | t3 | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | Using index |
    +----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------------+
  • id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行。

    1
    2
    3
    4
    5
    6
    7
    8
    mysql> explain select t2.* from t2 where id = (select id from t1 where id = (select t3.id from t3 where t3.other_column = ''));
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
    | 1 | PRIMARY | t2 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
    | 2 | SUBQUERY | t1 | const | PRIMARY | PRIMARY | 4 | | 1 | Using index |
    | 3 | SUBQUERY | t3 | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
  • id 有相同也有不同,id 值越大,优先级越高,越先执行;id 相同,则认为是一组,从上往下顺序执行。

    1
    2
    3
    4
    5
    6
    7
    8
    mysql> explain select t2.* from t2 where id = (select id from t1 where id = (select t3.id from t3 where t3.other_column = ''));
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
    | 1 | PRIMARY | t2 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
    | 2 | SUBQUERY | t1 | const | PRIMARY | PRIMARY | 4 | | 1 | Using index |
    | 3 | SUBQUERY | t3 | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+

select_type

含义:查询类型,主要用于区别普通查询、联合查询、子查询等复杂查询。

六种类型

  • SIMPLE:简单的 select 查询,查询中不包含子查询或者 union 联合查询。
  • PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为 PRIMARY
  • SUBQUERY:在 selectwhere 列表中包含了子查询。
  • DERIVED:在 from 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询,把结果放在临时表里。
  • UNION:若第二个 select 出现在 union 之后,则被标记为 union;若 union 包含在 from 字句的查询中,外层 select 将被标记为 DERIVED
  • UNION RESULT:两种 union 语句的合并。

table

含义:查询涉及的表或者衍生表。

type

含义:显示连接使用的类型。常见的有7种,从最好到最差排序:system > const > eq_ref > ref > range > index > all全表扫描),一般来说 至少达到 range 级别,最好达到 ref

七种类型

  • system:表只有一行记录,这是 const 类型的特例,只能用于 myisammemory 表,可以忽略不计。

  • const:表示通过索引一次就找到了,const 用于 primary key 或者 unique 索引,因为只匹配一行数据,所以很快,如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。

1
2
3
4
5
6
7
mysql> explain select * from (select * from t1 where id =1) d1;
+----+-------------+------------+--------+---------------+---------+---------+------+----
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+------+----
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 2 | DERIVED | t1 | const | PRIMARY | PRIMARY | 4 | | 1 | |
+----+-------------+------------+--------+---------------+---------+---------+------+----
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。
1
2
3
4
5
6
7
mysql> explain select * from t1,t2 where t1.id = t2.id;
+----+-------------+-------+--------+---------------+---------+---------+---------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+---------------
| 1 | SIMPLE | t1 | ALL | PRIMARY | NULL | NULL | NULL | 5 | |
| 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | |
+----+-------------+-------+--------+---------------+---------+---------+---------------
  • ref:非唯一索引或者主键扫描,或者使用了最左前缀规则索引的查询,它返回匹配某个单独值的所有行,然而它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体。
1
2
3
4
5
6
7
mysql> create index idx_name on t1(name);
mysql> explain select * from t1 where name ='Tim';
+----+-------------+-------+------+---------------+------+---------+-------+------+------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+------
| 1 | SIMPLE | t1 | ref | idx_name | idx_name | 92 | const | 1 | Using where |
+----+-------------+-------+------+---------------+------+---------+-------+------+------
  • range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的 where语句中出现了 between<>in 等查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
1
2
3
4
5
6
mysql> explain select * from t1 where id between 2 and 5;
+----+-------------+-------+-------+---------------+---------+---------+------+------+---
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+------+---
| 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 3 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+------+---
  • index:表示全索引扫描 (full index scan),indexall 区别为 index 类型只扫描所有的索引不扫描数据,通常比 all 快,因为索引文件通常比数据文件小。
1
2
3
4
5
6
mysql> explain select id from t1;
+----+-------------+-------+-------+---------------+---------+---------+------+------+---
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+------+---
| 1 | SIMPLE | t1 | index | NULL | PRIMARY | 4 | NULL | 6 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+---
  • all:表示全表扫描,这个类型的查询是性能最差的查询之一。通常来说,查询不应该出现 all 类型的查询, 因为这样的查询在数据量大的情况下,对数据库的性能是巨大的灾难。
1
2
3
4
5
6
mysql> explain select * from t2 where other_column = '';
+----+-------------+-------+------+---------------+------+---------+------+------+-------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------
| 1 | SIMPLE | t2 | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------

possible_keys

含义:显示可能应用在这张表中的索引。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

key

含义:实际使用的索引。如果为 NULL,则没有使用索引,key 参数可以作为使用了索引的判断标准。

key_len

含义:使用的索引的长度。key_len 显示的值为索引字段的最大可能长度,并非实际使用长度。在不损失精确性的情况下,长度越短越好。

ref

含义:显示索引的哪一列被使用了,如果可能的话是一个常数,哪些列或常量被用于查找索引列上的值。

rows

含义:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,当然越小越好。

Tips:explain 结果中,join 的查询代价可以用依次连乘 rows 估算。

Extra

含义:附加信息,提供了与操作有关联的信息。

三种类型

  • Using filesort:比较危险。说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称之为文件排序,需要进行额外的步骤来发现如何对返回的行排序。
1
2
3
4
5
6
mysql> explain select other_column from t1 where other_column = 'A' order by other_column3;
+----+-------------+-------+------+---------------+------+---------+-------+------+---------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+---------
| 1 | SIMPLE | t1 | ref | idx | idx | 92 | const | 1 | Using where; Using filesort |
+----+-------------+-------+------+---------------+------+---------+-------+------+---------

优化案例:

1
2
3
4
5
6
7
mysql> create index u_idx2 on t1(other_column,other_column2,other_column3);
mysql> explain select other_column from t1 where other_column = 'A' order by other_column2,other_column3;
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------
| 1 | SIMPLE | t1 | ref | idx,u_idx2 | u_idx2 | 92 | const | 1 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------
  • Using temporary:比较危险,使用了临时表保存中间结果。这通常发生在对不同的列集进行 ORDER BY 上和 GROUP BY 上,拖慢了查询速度。
1
2
3
4
5
6
mysql> explain select other_column from t1 where other_column in ('A','B','C') group by other_column3;
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| 1 | SIMPLE | t1 | range | idx,u_idx2 | u_idx2 | 92 | NULL | 3 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------

优化案例:

1
2
3
4
5
6
mysql> explain select other_column from t1 where other_column in ('A','B','C') group by other_column,other_column2;
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| 1 | SIMPLE | t1 | range | idx,u_idx2 | u_idx2 | 92 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
  • Using index:表示相应的 select 操作中使用了覆盖索引,避免访问了表的数据行,效率不错。如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现 using where,表明索引用来读取数据而非执行查找动作。

    覆盖索引: 就是 select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。注意的是,如果要使用覆盖索引,一定要注意 select 列表中只读取出需要的列,而不是 select *,因为如果将所有字段一起做索引会导致索引文件过大,降低查询性能。

1
2
3
4
5
6
mysql> explain select other_column from t1 where other_column in ('A','B','C') group by other_column,other_column2;
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| 1 | SIMPLE | t1 | range | idx,u_idx2 | u_idx2 | 92 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
1
2
3
4
5
6
mysql> explain select other_column,other_column2,other_column3 from t1;
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------
| 1 | SIMPLE | t1 | index | NULL | u_idx2 | 276 | NULL | 6 | Using index |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------

Tips: 在 show table status 结果中看到的 Rows 用于表示表的当前行数。对于 MyISAM 表这是一个精确值,但对InnoDB 这是个估算值。