Explain执行计划详解
SQL语句的中关键字执行顺序须知
关键字简述:
- FROM:确定数据来源,即指定表;
- JOIN...ON:确定关联表和关联条件;
- WHERE:指定过滤条件,过滤出满足条件的数据;
- GROUP BY:按指定的字段对过滤后的数据进行分组;
- HAVING:对分组之后的数据指定过滤条件;
- SELECT:查找想要的字段数据;
- DISTINCT:针对查找出来的数据进行去重;
- ORDER BY:对去重后的数据指定字段进行排序;
- LIMIT:对去重后的数据限制获取到的条数,即分页;
Explain有什么用
当Explain
与 SQL
语句一起使用时,MySQL
会显示来自优化器关于SQL执行的信息。也就是说,MySQL
解释了它将如何处理该语句,包括如何连接表以及什么顺序连接表等。
- 表的加载顺序
sql
的查询类型- 可能用到哪些索引,哪些索引又被实际使用
- 表与表之间的引用关系
- 一个表中有多少行被优化器查询 .....
Explain执行计划详解
id
id:
:表示查询中执行select子句或者操作表的顺序,id
的值越大,代表优先级越高,越先执行。 id
大致会出现 3种情况:
1、id
相同
看到三条记录的id
都相同,可以理解成这三个表为一组,具有同样的优先级,执行顺序由上而下,具体顺序由优化器决定。
mysql> EXPLAIN SELECT * FROM one o,two t, three r WHERE o.two_id = t.two_id AND t.three_id = r.three_id;
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | o | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100 | NULL |
| 1 | SIMPLE | t | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | r | NULL | eq_ref | PRIMARY | PRIMARY | 4 | xin-slave.t.three_id | 1 | 100 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------------+------+----------+----------------------------------------------------+
2、id
不同
如果我们的 SQL
中存在子查询,那么 id
的序号会递增,id
值越大优先级越高,越先被执行 。当三个表依次嵌套,发现最里层的子查询 id
最大,最先执行。
mysql> EXPLAIN select * from one o where o.two_id = (select t.two_id from two t where t.three_id = (select r.three_id from three r where r.three_name='我是第三表2'));
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | PRIMARY | o | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50 | Using where |
| 2 | SUBQUERY | t | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50 | Using where |
| 3 | SUBQUERY | r | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
3、以上两种同时存在
将上边的 SQL
稍微修改一下,增加一个子查询,发现 id
的以上两种同时存在。相同id
划分为一组,这样就有三个组,同组的从上往下顺序执行,不同组 id
值越大,优先级越高,越先执行。
mysql> EXPLAIN select * from one o where o.two_id = (select t.two_id from two t where t.three_id = (select r.three_id from three r where r.three_name='我是第三表2')) AND o.one_id in(select one_id from one where o.one_name="我是第一表2");
+----+-------------+-------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
| 1 | PRIMARY | o | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50 | Using where |
| 1 | PRIMARY | one | NULL | eq_ref | PRIMARY | PRIMARY | 4 | xin-slave.o.one_id | 1 | 100 | Using index |
| 2 | SUBQUERY | t | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50 | Using where |
| 3 | SUBQUERY | r | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50 | Using where |
+----+-------------+-------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
select_type
select_type
:表示 select
查询的类型,主要是用于区分各种复杂的查询,例如:普通查询
、联合查询
、子查询
等。
1、SIMPLE
SIMPLE
:表示最简单的 select 查询语句,也就是在查询中不包含子查询或者 union
交并差集等操作。
2、PRIMARY
PRIMARY
:当查询语句中包含任何复杂的子部分,最外层查询则被标记为PRIMARY
。
3、SUBQUERY
SUBQUERY
:当 select
或 where
列表中包含了子查询,该子查询被标记为:SUBQUERY
。
4、DERIVED
DERIVED
:表示包含在from
子句中的子查询的select,在我们的 from
列表中包含的子查询会被标记为derived
。
5、UNION
UNION
:如果union
后边又出现的select
语句,则会被标记为union
;若 union
包含在 from
子句的子查询中,外层 select
将被标记为 derived
。
6、UNION RESULT
UNION RESULT
:代表从union
的临时表中读取数据,而table
列的<union1,4>
表示用第一个和第四个select
的结果进行union
操作。
mysql> EXPLAIN select t.two_name, ( select one.one_id from one) o from (select two_id,two_name from two where two_name ='') t union (select r.three_name,r.three_id from three r);
+------+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | two | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50 | Using where |
| 2 | SUBQUERY | one | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100 | Using index |
| 4 | UNION | r | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100 | NULL |
| NULL | UNION RESULT | <union1,4> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
-
table:输出结果集的表(表别名)
-
type:表示MySQL在表中找到所需行的方式,或者叫访问类型。常见访问类型如下,从上到下,性能由差到最好:
ALL 全表扫描 index 索引全扫描 range 索引范围扫描 ref 非唯一索引扫描 eq_ref 唯一索引扫描 const,system 单表最多有一个匹配行 NULL 不用扫描表或索引 -
type=ALL,全表扫描,MySQL遍历全表来找到匹配行
一般是没有where条件或者where条件没有使用索引的查询语句
-
type=index,索引全扫描,MySQL遍历整个索引来查询匹配行,并不会扫描表
一般是查询的字段都有索引的查询语句
index
:Index
与ALL
其实都是读全表,区别在于index
是遍历索引树读取,而ALL
是从硬盘中读取。 -
type=range,索引范围扫描,常用于<、<=、>、>=、between等操作
注意这种情况下比较的字段是需要加索引的,如果没有索引,则MySQL会进行全表扫描,如下面这种情况,create_date字段没有加索引:
EXPLAIN SELECT * FROM customer WHERE create_date>='2006-02-13' ;
-
type=ref,使用非唯一索引或唯一索引的前缀扫描,返回匹配某个单独值的记录行
store_id
字段存在普通索引(非唯一索引)ref类型还经常会出现在join操作中:
customer、payment表关联查询,关联字段
customer.customer_id
(主键),payment.customer_id
(非唯一索引)。表关联查询时必定会有一张表进行全表扫描,此表一定是几张表中记录行数最少的表,然后再通过非唯一索引寻找其他关联表中的匹配行,以此达到表关联时扫描行数最少。 -
type=eq_ref,类似ref,区别在于使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配
eq_ref一般出现在多表连接时使用primary key或者unique index作为关联条件。
-
type=const/system,单表中最多有一条匹配行,查询起来非常迅速,所以这个匹配行的其他列的值可以被优化器在当前查询中当作常量来处理
const/system出现在根据主键primary key或者 唯一索引 unique index 进行的查询
-
type=NULL,MySQL不用访问表或者索引,直接就能够得到结果
-
key
key
:区别于possible_keys
,key是查询中实际使用到的索引,若没有使用索引,显示为NULL
。具体请参考上边的例子。
当
type
为index_merge
时,可能会显示多个索引。
key_len
key_len
:表示查询用到的索引长度(字节数),原则上长度越短越好 。
- 单列索引,那么需要将整个索引长度算进去;
- 多列索引,不是所有列都能用到,需要计算查询中实际用到的列。
注意:
key_len
只计算where
条件中用到的索引长度,而排序和分组即便是用到了索引,也不会计算到key_len
中。
rows
rows
:以表的统计信息和索引使用情况,估算要找到我们所需的记录,需要读取的行数。
这是评估SQL
性能的一个比较重要的数据,mysql
需要扫描的行数,很直观的显示 SQL
性能的好坏,一般情况下 rows
值越小越好。
Extra
Extra
:不适合在其他列中显示的信息,Explain
中的很多额外的信息会在 Extra
字段显示。
1、Using index
Using index
:我们在相应的 select
操作中使用了覆盖索引,通俗一点讲就是查询的列被索引覆盖,使用到覆盖索引查询速度会非常快,SQl
优化中理想的状态。
什么又是覆盖索引?
一条 SQL
只需要通过索引就可以返回,我们所需要查询的数据(一个或几个字段),而不必通过二级索引,查到主键之后再通过主键查询整行数据(select *
)。
one_id
表为主键
mysql> EXPLAIN SELECT one_id from one ;
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | one | NULL | index | NULL | idx_two_id | 5 | NULL | 3 | 100 | Using index |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
注意:想要使用到覆盖索引,我们在 select
时只取出需要的字段,不可select *
,而且该字段建了索引。
mysql> EXPLAIN SELECT * from one ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | one | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
2、Using where
Using where
:查询时未找到可用的索引,进而通过where
条件过滤获取所需数据,但要注意的是并不是所有带where
语句的查询都会显示Using where
。
下边示例create_time
并未用到索引,type
为 ALL
,即MySQL
通过全表扫描后再按where
条件筛选数据。
mysql> EXPLAIN SELECT one_name from one where create_time ='2020-05-18';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | one | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
3、Using temporary
Using temporary
:表示查询后结果需要使用临时表来存储,一般在排序或者分组查询时用到。
mysql> EXPLAIN SELECT one_name from one where one_id in (1,2) group by one_name;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | one | NULL | range| NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using temporary; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
4、Using filesort
Using filesort
:表示无法利用索引完成的排序操作,也就是ORDER BY
的字段没有索引,通常这样的SQL都是需要优化的。
mysql> EXPLAIN SELECT one_id from one ORDER BY create_time;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | one | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
如果ORDER BY
字段有索引就会用到覆盖索引,相比执行速度快很多。
mysql> EXPLAIN SELECT one_id from one ORDER BY one_id;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | one | NULL | index | NULL | PRIMARY | 4 | NULL | 3 | 100 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
5、Using join buffer
Using join buffer
:在我们联表查询的时候,如果表的连接条件没有用到索引,需要有一个连接缓冲区来存储中间结果。
先看一下有索引的情况:连接条件 one_name
、two_name
都用到索引。
mysql> EXPLAIN SELECT one_name from one o,two t where o.one_name = t.two_name;
+----+-------------+-------+------------+-------+---------------+----------+---------+----------------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+----------------------+------+----------+--------------------------+
| 1 | SIMPLE | o | NULL | index | idx_name | idx_name | 768 | NULL | 3 | 100 | Using where; Using index |
| 1 | SIMPLE | t | NULL | ref | idx_name | idx_name | 768 | xin-slave.o.one_name | 1 | 100 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+----------------------+------+----------+--------------------------+
接下来删掉 连接条件 one_name
、two_name
的字段索引。发现Extra
列变成 Using join buffer
,type
均为全表扫描,这也是SQL
优化中需要注意的地方。
mysql> EXPLAIN SELECT one_name from one o,two t where o.one_name = t.two_name;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100 | NULL |
| 1 | SIMPLE | o | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+