条查询语句在经过MySQL
查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划
,这个执行计划展示了接下来具体执行查询的方式,
一条查询语句里边可以包含若干个SELECT
关键字,每个SELECT
关键字代表着一个小的查询语句,而每个SELECT
关键字的FROM
子句中都可以包含若干张表(连接查询),每一张表都对应着执行计划输出中的一条记录
不同的数据 也会导致执行计划不一样(比如测试环境和生产环境)
一般内容:
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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
列名 | 描述 |
---|---|
id |
在一个大的查询语句中每个SELECT 关键字都对应一个唯一的id |
select_type |
SELECT 关键字对应的那个查询的类型 |
table |
表名 |
partitions |
匹配的分区信息 |
type |
针对单表的访问方法 |
possible_keys |
可能用到的索引 |
key |
实际上使用的索引 |
key_len |
实际使用到的索引长度 |
ref |
当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows |
预估的需要读取的记录条数 |
filtered |
某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra |
一些额外的信息 |
总结:一个select一个id,id相同了说明是在同一个select
在一个大的查询语句中每个SELECT
关键字都对应一个唯一的id
比如 EXPLAIN SELECT * FROM s1 INNER JOIN s2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL |
| 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9954 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
比如 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a');
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a');
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+
| 1 | SIMPLE | s2 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 9954 | 10.00 | Using where; Start temporary |
| 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | s2.key3 | 1 | 100.00 | End temporary |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+
2 rows in set, 1 warning (0.00 sec)
看起来是两个select应该有两个不同的id ,但是但是执行计划中s1
和s2
表对应的记录的id
值全部是1
,这就表明了查询优化器将子查询转换为了连接查询(也就是连表)。
意义:每一个SELECT
关键字代表的小查询 这个小查询在整个大查询中扮演了一个什么角色
名称 | 描述 |
---|---|
SIMPLE |
查询语句中不包含UNION 或者子查询的查询都算作是SIMPLE 类型,但是可能包括连表 |
PRIMARY |
对于包含UNION 、UNION ALL 或者子查询的大查询来说,最左边的那个查询的select_type 值就是PRIMARY |
UNION |
对UNION 或者UNION ALL 的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type 值就是UNION |
UNION RESULT |
MySQL选择使用临时表来完成 UNION查询的去重工作,针对该临时表的查询的 select_type就是 UNION RESULT |
SUBQUERY |
如果包含子查询的查询语句不能够转为对应的semi-join (优化成连表)的形式 |
DEPENDENT SUBQUERY |
如果包含子查询的查询语句不能够转为对应的semi-join 的形式,并且该子查询是相关子查询(就是条件在外面),则该子查询的第一个SELECT 关键字代表的那个查询的select_type 就是DEPENDENT SUBQUERY 。比如EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a'; |
DEPENDENT UNION |
在包含UNION 或者UNION ALL 的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的select_type 的值就是DEPENDENT UNION 。 |
DERIVED |
Derived table |
MATERIALIZED |
Materialized subquery |
UNCACHEABLE SUBQUERY |
A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query |
UNCACHEABLE UNION |
The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY) |
一般为null 不介绍
一条记录就代表着MySQL对某个表的执行查询时的访问方法,其中的type
列就表明了这个访问方法是什么
性能从高到低:
名称 | 描述 |
---|---|
system |
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory |
const |
根据主键或者唯一二级索引列与常数进行等值匹配时。就是直接一步走索引的 |
eq_ref |
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的 |
ref |
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,就是先走二级索引,再等值去主索引 |
ref_or_null |
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL 值时,EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL |
index_merge |
索引合并的方法,EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a' |
unique_subquery |
|
index_subquery |
|
range |
如果使用索引获取某些范围区间 的记录,那么就可能使用到range 访问方法() |
index |
遍历二级索引,当我们可以使用索引覆盖,但需要扫描全部的索引记录时,比如:SELECT key_part1, key_part2, key_part3 FROM single_table WHERE key_part2 = 'abc'; |
ALL |
全表 |
一些例子 进行说明:
eq_ref
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
,比方说:
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------------+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 9688 | 100.00 | NULL |
| 1 | SIMPLE | s2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | xiaohaizi.s1.id | 1 | 100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------------+------+----------+-------+
2 rows in set, 1 warning (0.01 sec)
从执行计划的结果中可以看出,MySQL
打算将s1
作为驱动表,s2
作为被驱动表,重点关注s2
的访问方法是eq_ref
,表明在访问s2
表的时候可以通过主键的等值匹配来进行访问。
possible_keys:对某个表执行单表查询时可能用到的索引有哪些,key
:表示实际用到的索引有哪些
possible_keys列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间,所以如果可以的话,尽量删除那些用不到的索引。
还有可能possible_keys为空,key为某个索引
key_len
列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,它是由这三个部分构成的:
VARCHAR(100)
,使用的字符集是utf8
,那么该列实际占用的最大存储空间就是100 × 3 = 300
个字节。NULL
值,则key_len
比不可以存储NULL
值时多1个字节。反应的是type的表访问方法
是在访问方法是const
、eq_ref
、ref
、ref_or_null
、unique_subquery
、index_subquery
其中之一时,ref
列展示的就是与索引列作等值匹配的东东是什么,比如只是一个常数(const)或者是某个列。
查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows
列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows
列就代表预计扫描的索引记录行数。
单表看这个一般没用 更关注在连接查询中驱动表对应的执行计划记录的filtered
值
顾名思义,额外信息
这里只列一些常用的
Using index
查询以及搜索条件只包含属于某个索引的列(索引覆盖)
Using index condition
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b'; +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+ | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 266 | 100.00 | Using index condition | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+ 1 row in set, 1 warning (0.01 sec)
有些搜索条件中虽然出现了索引列,但却不能完全使用索引
比如上面的sql, 先根据
key1 > 'z'
这个条件,从二级索引idx_key1
中获取到对应的二级索引记录,先不着急回表,而是先检测一下该记录是否满足key1 LIKE '%a'
,再回表(他们的这个改进称之为索引条件下推
(英文名:Index Condition Pushdown
)也就是Using index condition
Using where
全表扫描并且,该语句的
WHERE
子句中有针对该表的搜索条件,或者是走了索引,但是还需要再过滤一次
id相同:执行顺序由上至下
id不同,id值越大优先级越高,越先被执行(子查询先查)
EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a'
中间加上FORMAT=JSON
在MySQL 5.6
以及之后的版本,查看优化器生成执行计划的整个过程
参考