MySQL执行计划EXPLAIN
数据库接收到SQL后的处理流程
#
概述
MYSQL官方的解释:当EXPLAIN
与可解释的语句一起使用时,MySQL将显示来自优化器的有关语句执行计划的信息。就是说,MySQL解释了它将如何处理该语句,包括有关如何连接表以及以何种顺序连接表的信息。
EXPLAIN如何使用?怎样查看执行计划?
mysql> explain select * from t_user;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | t_user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set (0.06 sec)
Explain
执行计划包含信息如下:分别是 id
、select_type
、table
、partitions
、type
、possible_keys
、key
、key_len
、ref
、rows
、filtered
、Extra
12个字段。
柱 | JSON名称 | 含义 |
---|---|---|
id | select_id | 该SELECT 标识符 |
select_type | 没有 | 该SELECT 类型 |
table | table_name | 输出行表 |
partitions | partitions | 匹配的分区 |
type | access_type | 联接类型 |
possible_keys | possible_keys | 可能的索引选择 |
key | key | 实际选择的索引 |
key_len | key_length | 所选键的长度 |
ref | ref | 与索引比较的列 |
rows | rows | 估计要检查的行 |
filtered | filtered | 按表条件过滤的行百分比 |
Extra | 没有 | 附加信息 |
准备工作,创建几张简单的表
CREATE TABLE `t_user` (
`USER_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`USER_NAME` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
`MOBILE` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
`EMAIL` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
`NICK_NAME` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
`DETAIL_ID` int(11) DEFAULT NULL COMMENT '详情ID',
PRIMARY KEY (`USER_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-------------------------------------------------------------------------------------------------------
CREATE TABLE `t_user_detail` (
`DETAIL_ID` int(11) NOT NULL COMMENT '详情ID',
`USER_ID` int(11) DEFAULT NULL COMMENT '用户ID',
`ADDRESS` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '地址',
`PROVINCE` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '省',
`CITY` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '市',
`AREA` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '区',
`MEMBER_ID` int(11) DEFAULT NULL COMMENT '会员ID',
PRIMARY KEY (`DETAIL_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-------------------------------------------------------------------------------------------------------
CREATE TABLE `t_member` (
`MEMBER_ID` int(11) NOT NULL COMMENT '会员ID',
`MEMBER_NAME` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '会员名称',
`MEMBER_CODE` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '会员编码',
`MEMBER_TYPE` smallint(8) DEFAULT NULL COMMENT '会员类型',
PRIMARY KEY (`MEMBER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
详解
id
id:
:表示查询中执行select子句或者操作表的顺序,id
的值越大,代表优先级越高,越先执行。
mysql> explain select * from t_user u ,t_user_detail d where u.detail_id=d.detail_id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | d | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set (0.15 sec)
id
值相同,代表具有同样的优先级,执行顺序由上而下,具体顺序由优化器决定。
mysql> explain select * from t_user u where u.detail_id = (select detail_id from t_user_detail);
+----+-------------+---------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
| 2 | SUBQUERY | t_user_detail | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+---------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
- 如果我们的
SQL
中存在子查询,那么id
的序号会递增,id
值越大优先级越高,越先被执行 。当三个表依次嵌套,发现最里层的子查询id
最大,最先执行
select_type
表示 select
查询的类型,主要是用于区分各种复杂的查询,例如:普通查询
、联合查询
、子查询
等
- SIMPLE
表示最简单的 select 查询语句,也就是在查询中不包含子查询或者 union
交并差集等操作。
mysql> explain select * from t_user;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | t_user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
- PRIMARY
当查询语句中包含任何复杂的子部分,最外层查询则被标记为PRIMARY
。
explain select * from t_user where detail_id = (select detail_id from t_user_detail d join t_member m on d.member_id=m.member_id where m.member_type=1 );
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | PRIMARY | t_user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
| 2 | SUBQUERY | m | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50.00 | Using where |
| 2 | SUBQUERY | d | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
- SUBQUERY
当 select
或 where
列表中包含了子查询,该子查询被标记为:SUBQUERY
。
- DERIVED
派生表:表示包含在from
子句中的子查询的select,在我们的 from
列表中包含的子查询会被标记为derived
- UNION
如果union
后边又出现的select
语句,则会被标记为union
;若 union
包含在 from
子句的子查询中,外层 select
将被标记为 derived
。
- UNION RESULT
代表从union
的临时表中读取数据,而table
列的<union1,2>
表示用第一个和第二个select
的结果进行union
操作
mysql> explain select * from t_user where user_id=1 union select * from t_user where user_id=2;
+------+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | t_user | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | t_user | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
table
查询的表名,并不一定是真实存在的表,有别名显示别名,也可能为临时表,例如上边的DERIVED
、 <union1,4>
等。
partitions
查询时匹配到的分区信息,对于非分区表值为NULL
,当查询的是分区表时,partitions
显示分区表命中的分区情况。
type
查询使用了何种类型,它在 SQL
优化中是一个非常重要的指标,以下性能从好到坏依次是:system
> const
> eq_ref
> ref
> ref_or_null
> index_merge
> unique_subquery
> index_subquery
> range
> index
> ALL
- system
当表仅有一行记录时(系统表),数据量很少,往往不需要进行磁盘IO,速度非常快。
- const
表示查询时命中 primary key
主键或者 unique
唯一索引,或者被连接的部分是一个常量(const
)值。
- eq_ref
查询时命中主键primary key
或者 unique key
索引, type
就是 eq_ref
。
- ref
区别于eq_ref
,ref
表示使用非唯一性索引,会找到很多个符合条件的行。
- ref_or_null
这种连接类型类似于 ref,区别在于 MySQL
会额外搜索包含NULL
值的行。
- index_merge
使用了索引合并优化方法,查询使用了两个以上的索引。
- unique_subquery
子查询返回不重复的集合。
- index_subquery
区别于unique_subquery
,用于非唯一索引,可以返回重复值。
- range
使用索引选择行,仅检索给定范围内的行。简单点说就是针对一个有索引的字段,给定范围检索数据。在where
语句中使用 bettween...and
、<
、>
、<=
、in
等条件查询 type
都是 range
。
- index
Index
与ALL
其实都是读全表,区别在于index
是遍历索引树读取,而ALL
是从硬盘中读取。
- ALL
将遍历全表以找到匹配的行,性能最差。
possible_keys
该possible_keys
列指示MySQL可以选择从中查找表中各行的索引。请注意,此列完全独立于的输出中显示的表顺序 EXPLAIN
。这意味着,某些键possible_keys
实际上可能无法用于生成的表顺序。
key
区别于possible_keys
,key是查询中实际使用到的索引,若没有使用索引,显示为NULL
。
key_len
表示查询用到的索引长度(字节数),原则上长度越短越好 。
- 单列索引,那么需要将整个索引长度算进去;
- 多列索引,不是所有列都能用到,需要计算查询中实际用到的列。
注意:
key_len
只计算where
条件中用到的索引长度,而排序和分组即便是用到了索引,也不会计算到key_len
中。
ref
常见的有:const
,func
,null
,字段名。
- 当使用常量等值查询,显示
const
, - 当关联查询时,会显示相应关联表的
关联字段
- 如果查询条件使用了
表达式
、函数
,或者条件列发生内部隐式转换,可能显示为func
- 其他情况
null
rows
以表的统计信息和索引使用情况,估算要找到我们所需的记录,需要读取的行数。这个值越少越好。
filtered
百分比值。表里符合条件的记录数的百分比。
Extra
不适合在其他列显式的值都出现在这里。
- Using index
使用了覆盖索引。
- Using where
查询时未找到可用的索引,进而通过where
条件过滤获取所需数据。
- Using temporary
表示查询后结果需要使用临时表来存储,一般在排序或者分组查询时用到。
mysql> explain select * from t_user where user_id=1 union select * from t_user where user_id=2;
+------+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | t_user | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | t_user | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
- Using filesort
mysql> explain select * from t_user order by user_name;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | t_user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | Using filesort |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
- Using join buffer
在我们联表查询的时候,如果表的连接条件没有用到索引,需要有一个连接缓冲区来存储中间结果。
mysql> explain select * from t_user u join t_user_detail d on u.detail_id=d.detail_id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | d | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
- No tables used
mysql> explain select MD5(UUID());
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
总结
执行计划是我们进行SQL查询优化的重要工具,需要明白各列的含义才能在开发中运用自如。
本文使用 tech.souyunku.com 排版