MySQL explain 字段详解
前言
我们平常在分析MySQL
中的慢SQL
时,经常会用到 explain
来分析 SQL
的执行情况。explain
有很多字段,比如:id
、select_type
、table
、partitions
、type
、possible_keys
、key
、key_len
、ref
、rows
、filtered
、Extra
等,
网上的各种文章看的眼花缭乱,还有不少错误。下面我主要参照MySQL
官方文档来分析下这些字段以及字段值都代表什么含义。
环境准备
MySQL
版本为5.7.36
,
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.36 |
+-----------+
5.7
和之前的版本还是有差别的,默认开启了一些优化指标,
我们可以通过 SELECT @@optimizer_switch\G
命令来查看 MySQL
中默认打开的指标开关如下:
SELECT @@optimizer_switch\G
*************************** 1. row ***************************
@@optimizer_switch:
index_merge=on,
index_merge_union=on,
index_merge_sort_union=on,
index_merge_intersection=on,
engine_condition_pushdown=on,
index_condition_pushdown=on,
mrr=on,mrr_cost_based=on,
block_nested_loop=on,
batched_key_access=off,
materialization=on,
semijoin=on,
loosescan=on,
firstmatch=on,
duplicateweedout=on,
subquery_materialization_cost_based=on,
use_index_extensions=on,
condition_fanout_filter=on,
derived_merge=on,
prefer_ordering_index=on
1 row in set (0.00 sec)
在接下来的某些字段讲解中,需要关掉某些指标开关才能看到这些字段值以及演示的效果。
我下面的案例只会关闭部分指标,如果对这些指标开关感兴趣的同学点击 MySQL官网 查看。
💡 所以你用
5.7
之前的版本和我这里的结果对比可能会有些许不同,望请知晓。
下面所有的表使用的数据库引擎都是InnoDB
。
用到的表:
t_order
:订单表
CREATE TABLE `t_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键自增id',
`cid` varchar(11) NOT NULL DEFAULT '' COMMENT '公司id',
`product_id` int(11) NOT NULL DEFAULT '0' COMMENT '产品id',
PRIMARY KEY (`id`),
KEY `idx_cid` (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
t_product
: 产品表
CREATE TABLE `t_product` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键id',
`cid` varchar(11) NOT NULL DEFAULT '' COMMENT '公司id',
PRIMARY KEY (`id`),
KEY `idx_cid` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
由于MySQL 5.7
中默认优化了很多指标来提升sql
的执行效率,
explain中的字段
mysql> explain select * from t_order where product_id =101;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 10.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
上面可以看到执行explain
之后出现的所有字段:id
、select_type
、table
、partitions
、type
、possible_keys
、key
、key_len
、ref
、rows
、filtered
、Extra
。
在
MySQL5.7
版本,使用explain
参数会自动带上extended
和partitions
,加上extended
会比之前的版本多一个filtered
字段,还会多显示了warning
。使用show warnings
命令可以看到优化器优化之后的查询语句,优化后的语句可能不符合sql
语法,但是MySQL
是可以识别的,下面的案例中我也会使用到show warnings
命令。加上partitions
会多一个分区字段,但是现在一般都不会再用分区了,所以基本上这个字段的值都是null
。
id
id
是序列号,sql
里面有几个select
,就有几个id
, 而不是查询了几张表,就有几个id
。
比如下面的这个sql
,只查了一个表,但是有两个select
,explain
之后是有两列,两个id
的。
explain select * from t_order where id = (select id from t_order where cid = '100000035');
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | t_order | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | t_order | NULL | ref | idx_cid | idx_cid | 35 | const | 1 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
2 rows in set, 1 warning (0.02 sec)
执行顺序是和id
相关的,id
越大越先执行,如果id
值相同,则从上到下执行。如果id
为null
则最后执行。什么情况下 id
会为null
呢?比如在使用 union
的时候:
mysql> explain select id from t_order where product_id = 101 union select id from t_product where id = 101;
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 10.00 | Using where |
| 2 | UNION | t_product | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
select_type
从官网文档可以看到 select_type
的所有值如下:
SIMPLE
: 简单查询(没有使用联合查询或者子查询)比如:
explain select * from t_order where product_id =101;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 10.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
PRIMARY
:复杂查询中的最外层子查询,比如下面id=1的这一列:
explain select * from t_order where id = (select id from t_order where cid = '100000035');
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | t_order | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | t_order | NULL | ref | idx_cid | idx_cid | 35 | const | 1 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
2 rows in set, 1 warning (0.01 sec)
SUBQUERY
: 子查询的第一个select,比如上面的sql中id为2的这一列DEPENDENT SUBQUERY
:依赖于外部查询的子查询。大致意思是这样,比如你执行下面的sql:
explain select * from t_product where id in (select product_id from t_order where cid = '10000005') ;
按我们预想的执行计划应该是先从 t_order
中 找到 cid = ‘10000005’ 的product_id
,然后再去 t_product
表中查找id相对应的数据。但是 MySQL 优化器可能把sql优化成下面这样子:
select * from t_product where exists (select * from t_order
where t_product.id=t_order.product_id
and t_order.cid = '10000005')
因为需要 product_id 字段,所以无法先执行子查询。这样的话就会导致扫描 t_product
全表,如果生产环境遇到这种情况一定要注意,可以改为 join
连接查询。
MySQL 5.7
版本对这种子查询默认做了优化。
explain select * from t_product where id in (select product_id from t_order where cid = '10000005') ;
+----+--------------+-------------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
| 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | t_product | NULL | eq_ref | PRIMARY | PRIMARY | 4 | <subquery2>.product_id | 1 | 100.00 | Using where |
| 2 | MATERIALIZED | t_order | NULL | ref | idx_cid | idx_cid | 35 | const | 1 | 100.00 | NULL |
+----+--------------+-------------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
3 rows in set, 1 warning (0.03 sec)
执行完上面的explain
之后,我们看到这里有一个:MATERIALIZED(物化,简单来说就是将子查询的结果存储为临时表,后面会讲到)
,还有一个1 warning
,然后我们使用 show warnings;
命令来查看优化后的sql
语句:
show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `seata_order`.`t_product`.`id` AS `id`,`seata_order`.`t_product`.`cid` AS `cid` from `seata_order`.`t_product` semi join (`seata_order`.`t_order`) where ((`seata_order`.`t_order`.`cid` = '10000005') and (`seata_order`.`t_product`.`id` = `<subquery2>`.`product_id`))
可以看到这里使用了 semi join
这种半连接的方式来进行了优化。
如果想看到之前版本未优化的sql
语句,我们可以先执行命令关闭materialization
和semijoin
开关:
set session optimizer_switch = 'materialization=off';
set session optimizer_switch = 'semijoin=off';
再次执行 explain
:
explain select * from t_product where id in (select product_id from t_order where cid = '10000005') ;
+----+--------------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+--------------------------+
| 1 | PRIMARY | t_product | NULL | index | NULL | idx_cid | 35 | NULL | 100 | 100.00 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | t_order | NULL | ref | idx_cid | idx_cid | 35 | const | 1 | 10.00 | Using where |
+----+--------------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+--------------------------+
2 rows in set, 1 warning (0.00 sec)
可以看到select_type
中已经有DEPENDENT SUBQUERY
了。
再次执行 show warnings;
:
show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `seata_order`.`t_product`.`id` AS `id`,`seata_order`.`t_product`.`cid` AS `cid` from `seata_order`.`t_product` where <in_optimizer>(`seata_order`.`t_product`.`id`,<exists>(/* select#2 */ select 1 from `seata_order`.`t_order` where ((`seata_order`.`t_order`.`cid` = '10000005') and (<cache>(`seata_order`.`t_product`.`id`) = `seata_order`.`t_order`.`product_id`))))
可以看到这里MySQL
是使用exists
来执行此语句的。
UNION
:在使用了UNION的情况下,比如上文中提到的id列为null的情况:
explain select id from t_order where product_id = 101 union select id from t_product where id = 101
DEPENDENT UNION
:依赖于外部查询的UNION,和上文讲到的DEPENDENT SUBQUERY
类似,由于 UNION 不经常使用,这里不再进行赘述。- UNION RESULT:UNION之后的结果,比如上文中提到的id列为null的情况
mysql> explain select id from t_order where product_id = 101 union select id from t_product where id = 101;
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 10.00 | Using where |
| 2 | UNION | t_product | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
3 rows in set, 1 warning (0.03 sec)
DERIVED
:派生表,from 当中的子查询派生出来的新表。
explain select * from (select distinct(id) from t_order) t1;
+----+-------------+------------+------------+-------+-----------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+-----------------+---------+---------+------+------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 100.00 | NULL |
| 2 | DERIVED | t_order | NULL | index | PRIMARY,idx_cid | idx_cid | 35 | NULL | 100 | 100.00 | Using index |
+----+-------------+------------+------------+-------+-----------------+---------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
并不是所有
from
中的子查询都会产生派生表,有个派生表的指标derived_merge=on
在MySQL 5.7
中默认是打开的。比如下面的sql
并不会出现DERIVED
。
explain select * from (select * from t_order where id =1) t1;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_order | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
这是因为 MySQL
优化器将派生表合并到外部查询中了,通过 show warnings;
可以看到真正执行的时候是没有子查询的,所以也就没有产生派生表。
show warnings;
+-------+------+------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select '1' AS `id`,'10000001' AS `cid`,'101' AS `product_id` from `seata_order`.`t_order` where 1 |
+-------+------+------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
我们可以通过关闭开关来查看未优化前的结果:
`set session optimizer_switch = 'derived_merge=off';`
explain select * from (select * from t_order where id =1) t1;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
| 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 | t_order | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
可以看到这里就产生了派生表。
MATERIALIZED
物化,上面在讲子查询时有提及到。它在SQL执行过程中,将子查询的结果集保存到一个临时表中,后序获取该结果集的的数据直接访问此临时表。UNCACHEABLE SUBQUERY
:不可缓存的子查询,对于外层的主表,子查询不可被物化,每次都需要重新计算获取数据,比较耗时UNCACHEABLE UNION
:不可缓存的 UNION,UNION结果不可被物化,每次都需要重新计算获取数据,比较耗时
table
这个比较简单,表名,代表访问的是哪个表或者是表的别名。
partitions
分区,如果使用了 MySQL
的表分区功能来存储数据,这里可以看到查询的是哪个分区。现在一般直接分表的,所以此字段看到的值基本都是 null
。
那么为什么现在基本不使用分区了呢?
- 底层实现的分区表,对
MySQL
来说是有性能消耗的,它需要扫描所有的分区列表。如果要操作数据,还需要锁住所有分区。 - 分区依然是单库,在一些需要高并发的场景中优化空间非常有限。面对日益增长的海量数据,优化存储能力有限。
总之,MySQL
的表分区实现偏底层,定制不灵活且性能有限,维护成本高,所以大多数情况下都是不建议使用的。
type
type
代表 MySQL
访问数据时要走的索引类型,可以参考 官网。
性能从好到差依次为:
system
> const
> eq_ref
> ref
> fulltext
> ref_or_null
> index_merge
> unique_subquery
> index_subquery
> range
> index
> ALL
。
官方给这种的性能好坏也不是绝对的,有可能有时候靠在前面的
type
比靠在后面的type
执行时间还要久,还和扫描的数据量有关,具体情况还是要以执行结果为准。
system
:
只有一行能匹配,它是const
的一种特例,比如上文中我们讲到的派生表:
`set session optimizer_switch = 'derived_merge=off';`
explain select * from (select * from t_order where id =1) t1;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
| 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 | t_order | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
可以看到,id
为1的这一列,type
就是 system
。 表示派生表中只有一条数据供他查询。
const
:
表示只能查询到一条数据,比如下面根据id查询:
explain select * from t_order where id = 2;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_order | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
这里我们也可以看到它和system
的不同,system
是已知派生表中只有一行数据能匹配。但是在 innodb
引擎中中是没有存储表的行数的,所以即使你表中只有一条数据,根据id查询,type
也是const
。
平常我们单表根据查询唯一索引(字段不允许为空)
或者主键索引
查询时,type
基本都是const
。
唯一索引
如果字段允许为空,该字段是可以有多个null
值的,如果这个时候我们用字段 is null
去查询,可能会有多个结果匹配,type
就是ref
而不是const
了。
eq_ref
:
在表连接中,驱动表中的每一条数据,被驱动表最多有一条数据与驱动表匹配(唯一非空索引
和主键索引
),它是表连接中最好的类型。
首先,我们给t_order
表的product_id
字段添加唯一索引:
alter table t_order add unique key `idx_pid` (product_id);
执行下面的语句可以看到对于被驱动表 t_order
的 type
就是 eq_ref
:
explain select * from t_product p left join t_order o on o.product_id = p.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+-------------+
| 1 | SIMPLE | p | NULL | index | NULL | idx_cid | 35 | NULL | 100 | 100.00 | Using index |
| 1 | SIMPLE | o | NULL | eq_ref | idx_pid | idx_pid | 4 | seata_order.p.id | 1 | 100.00 | Using where |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+-------------+
ref
:
等值查询,可能返回多条匹配记录,普通索引或者唯一索引可为null
的情况下都可能会出现ref
:
explain select * from t_order where cid = '100000062';
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_order | NULL | ref | idx_cid | idx_cid | 35 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+-------+
我们把 t_order
的 product_id
字段改为允许为null
的唯一索引,然后再执行explain
,可以看到type
也是ref
:
explain select * from t_order where product_id is null;
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | t_order | NULL | ref | idx_pid | idx_pid | 5 | const | 2 | 100.00 | Using index condition |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-----------------------+
-
fulltext
:
使用全文索引时才会出现的类型,全文索引可以简单的理解为类似ES的倒排索引,由于实验过于复杂,这里不再进行演示(个人觉得如果频繁应用这种场景的话,还不如直接上ES)。对全文索引感兴趣的同学可以参考大目的:MySQL 用全文索引解决模糊查询 -
ref_or_null
:
与前面的ref
类似,但是条件中多了为空判断,只有在字段允许为空的时候才会出现,比如我们将t_order
表中的cid
字段改为允许为null
之后,执行下面的语句就会出现ref_or_null
:
mysql> explain select * from t_order where cid = '100000062' or cid is null;
+----+-------------+--------------+------------+-------------+---------------+---------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+-------------+---------------+---------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | t_order | NULL | ref_or_null | idx_cid | idx_cid | 36 | const | 2 | 100.00 | Using index condition |
+----+-------------+--------------+------------+-------------+---------------+---------+---------+-------+------+----------+-----------------------+
index_merge
:
表示查询使用了多个索引,去后取交集或者并集,在OR
和AND
的条件中比较常见,比如下面的sql
:
explain select * from t_order where product_id =100 or cid = '100000062';
+----+-------------+---------+------------+-------------+-----------------+-----------------+---------+------+------+----------+-------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------------+-----------------+-----------------+---------+------+------+----------+-------------------------------------------+
| 1 | SIMPLE | t_order | NULL | index_merge | idx_pid,idx_cid | idx_pid,idx_cid | 4,35 | NULL | 2 | 100.00 | Using union(idx_pid,idx_cid); Using where |
+----+-------------+---------+------------+-------------+-----------------+-----------------+---------+------+------+----------+-------------------------------------------+
对索引合并算法感兴趣的同学可以参考:官网。
unique_subquery
where
条件的子查询中每一条数据只有一条数据与外部查询关联,它在子查询中查的字段是不重复的唯一值,类似于eq_ref
,只不过这个是在子查询中才会出现。
首先我们关闭物化
和半连接
的开关:
set session optimizer_switch = 'materialization=off';
set session optimizer_switch = 'semijoin=off';
执行下面的sql
:
explain select * from t_order where product_id in (select id from t_product);
+----+--------------------+-----------+------------+-----------------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-----------+------------+-----------------+---------------+---------+---------+------+------+----------+--------------------------+
| 1 | PRIMARY | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | t_product | NULL | unique_subquery | PRIMARY | PRIMARY | 4 | func | 1 | 100.00 | Using where; Using index |
+----+--------------------+-----------+------------+-----------------+---------------+---------+---------+------+------+----------+--------------------------+
id
在 t_product
表中是主键索引,这里的type
就是unique_subquery
。
index_subquery
index_subquery
和unique_subquery
类似,只不过unique_subquery
只对应一条数据,index_subquery
可能对应多条数据。它们两个之间的关系类似于eq_ref
和ref
的关系。
下面我用cid
字段来做实验,对该字段建立的是普通索引:
set session optimizer_switch = 'materialization=off';
set session optimizer_switch = 'semijoin=off';
explain select * from t_order where cid in (select cid from t_product);
+----+--------------------+-----------+------------+----------------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-----------+------------+----------------+---------------+---------+---------+------+------+----------+-------------+
| 1 | PRIMARY | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | t_product | NULL | index_subquery | idx_cid | idx_cid | 35 | func | 1 | 100.00 | Using index |
+----+--------------------+-----------+------------+----------------+---------------+---------+---------+------+------+----------+-------------+
可以看到这里的 type
是 index_subquery
。
range
:范围索引,比如我们常用的>
、<
、>=
、<=
、BETWEEN
、in
等操作。
explain select * from t_order where id >50;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 50 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
in
操作的type
也可能是range
:
explain select * from t_order where id in (50,51);
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
index
:表示扫描整个索引树才能拿到结果。
explain select cid from t_order;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | index | NULL | idx_cid | 35 | NULL | 100 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
统计表总行数:
explain select count(*) from t_order;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | index | NULL | idx_pid | 4 | NULL | 100 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
由于innodb
引擎没有存储表的总行数,所以它需要遍历一遍主键索引才能得到总行数。
all
:代表没有使用到索引,需要全表扫描。下面几个情况都可能导致全表扫描。- 查询全表、
- 查询条件字段没有加索引
- 有索引但是不符合
最左匹配原则
- 字段有索引但字段区分度低,
MySQL
觉得走索引还不如全表扫描 - 对查询字段使用了函数
- 对查询字段做了运算
- 关联字段类型不匹配
如果线上系统有type
为all
的情况我们一定要注意,如果表的数据量很大,会给服务器造成很大的压力,还会将这些数据放到MySQL
的Buff Pool
中,占用缓存空间。如果是 udpate
更新数据还可能锁全表。
由于全表扫描的例子比较多,这里就不一一举例,随便举个全模糊查询的例子:
explain select * from t_order where cid like '%666%';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
null
还有一种type
为null
的情况, 官网没写。null
表示MySQL
在优化分析阶段分析出不需要访问表或者索引就能得到预期的结果,它的性能比system
还要好。 比如我们执行下面的sql
:
explain select 1+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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
下面的这个min
函数的例子,type
也是null
:
explain select min(cid) from t_order;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| 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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
这里的 Extra
字段值是:Select tables optimized away
,它代表MySQL
做的一种优化,这时候MySQL
在优化阶段只需要访问 B+Tree
的最左节点即可得到数据,反之访问最右节点就能获得最大值。下面在讲Extra
字段的时候就不再重复讲Select tables optimized away
这个的含义了。
possible_keys
possible_keys
表示sql
执行时可能用到的索引,但是不一定就是走这些索引,具体走不走索引或者走哪个索引,还要以下面讲到的 key
为准。
key
key
表示sql
执行时实际走的索引名,key
为null
表示没有用到索引。
比如下面的语句用到了名称为idx_pid
的索引:
explain select * from t_order where product_id =101;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_order | NULL | const | idx_pid | idx_pid | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
key_len
key_len
表示MySql
在索引里使用的字节数,如果是联合索引的话,根据这个值我们可以计算出有几个字段用到了索引。
比如我们先删除t_order
表中cid
字段和product_id
字段的单独索引,再对他们建立联合索引,首先where
条件里只根据cid
去查:
EXPLAIN SELECT * FROM t_order WHERE cid = '10000001';
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ref | idx_cid_pid | idx_cid_pid | 35 | const | 1 | 100.00 | Using index |
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
cid
字段是VARCHAR
类型,一个长度占3个字节,还要预留两个字节来存储字符串长度,共:11*3+2=35个字节,说明这里只用到了cid
字段的索引。
然后我们再根据cid
和product_id
的条件一起去查:
EXPLAIN SELECT * FROM t_order WHERE cid = '10000001' AND product_id = 101;
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ref | idx_cid_pid | idx_cid_pid | 39 | const,const | 1 | 100.00 | Using index |
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------------+------+----------+-------------+
可以看到这里的key_len
为39,int
类型占4个字节,35(cid)+4(product_id)=39,说明用到了两个字段的索引。
这里的
ref
字段值为:“const,const”,也从侧面证明了是用到了两个字段的索引。
ref
ref 列表示在 key 列记录的索引中,表查找值所用到的列名或常量。
可能有如下几个值:
const
索引字段等值常量查询:
explain select * from t_order where product_id = 101;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_order | NULL | const | idx_pid | idx_pid | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
- 表连接中关联表所用到的字段名:
EXPLAIN SELECT * FROM t_order ,t_product WHERE t_order.product_id=t_product.id;
+----+-------------+-----------+------------+--------+---------------+---------+---------+--------------------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+--------+---------------+---------+---------+--------------------------------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | idx_pid | NULL | NULL | NULL | 100 | 100.00 | NULL |
| 1 | SIMPLE | t_product | NULL | eq_ref | PRIMARY | PRIMARY | 4 | seata_order.t_order.product_id | 1 | 100.00 | Using where |
+----+-------------+-----------+------------+--------+---------------+---------+---------+--------------------------------+------+----------+-------------+
上面第二条数据的ref
为product_id
字段,表明了两个表是通过product_id
字段进行关联的。
- 条件中发生了隐式转换或者使用了某些函数,比如我们上面讲到的
index_subquery
:
set session optimizer_switch = 'materialization=off';
set session optimizer_switch = 'semijoin=off';
explain select * from t_order where cid in (select cid from t_product);
+----+--------------------+-----------+------------+----------------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-----------+------------+----------------+---------------+---------+---------+------+------+----------+-------------+
| 1 | PRIMARY | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | t_product | NULL | index_subquery | idx_cid | idx_cid | 35 | func | 1 | 100.00 | Using index |
+----+--------------------+-----------+------------+----------------+---------------+---------+---------+------+------+----------+-------------+
这里的ref
就是func
了。通过show warnings;
可以看到这里是把in
转换为了exist
。
show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `seata_order`.`t_order`.`id` AS `id`,`seata_order`.`t_order`.`cid` AS `cid`,`seata_order`.`t_order`.`product_id` AS `product_id` from `seata_order`.`t_order` where <in_optimizer>(`seata_order`.`t_order`.`cid`,<exists>(<index_lookup>(<cache>(`seata_order`.`t_order`.`cid`) in t_product on idx_cid))) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- null 非等值查询或者没有用到索引,
ref
基本都是null,比如上面提到的全模糊查询:
explain select * from t_order where cid like '%666%';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
rows
rows
表示MySQL
预估要扫描的行数,这个是估值,并不一定是实际所扫描的行数。
filtered
filtered
这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,这里的值是比例。
explain select * from t_order where product_id >101;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | idx_pid | NULL | NULL | NULL | 100 | 99.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
比如这里预估扫描100行,filtered
是99%,也就是预估在存储引擎层扫描了100行数据,在server
层过滤后,返回了99行数据。
Extra
Extra
包含了 MySQL
如何解析查询的附加信息,我这里会选一些经常看到的字段值进行演示,如果要看所有的值,请查看 官方文档。
Using where
代表数据库引擎(innodb)返回结果后server
层还要再次筛选。
这个网上有各种说法,有些文章说出现了Using where
是没有用到索引,这个结论是错误的。我这里以高性能mysql第三版说法为准。
首先我们来看下MySQL
的体系架构:
上面是server
层,下面是存储引擎层(Innodb、Myisam、Memory等)。我理解就是Using where
从存储引擎拿到数据后需要在server层进行再次筛选。
💡 用到了索引和没有用到索引,都可能会出现using where。
走了product_id
字段索引的案例:
explain select * from t_order where product_id >150;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | idx_pid | NULL | NULL | NULL | 100 | 50.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
预计在数据库索引层扫描100行,然后在server
层过滤了50%。
没有用到索引的案例:
explain select * from t_order where cid like '%666%';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
这里将所有数据返回server层再进行筛选。
Using index
:表示使用到了覆盖索引,不需再进行回表查询,效率较好。比如下面这个sql
,通过扫描cid
字段建立二级索引就能拿到结果,Extra
就是Using index
。
explain select cid from t_order;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | index | NULL | idx_cid | 35 | NULL | 100 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
Using filesort
表示MySQL
有可能要使用文件排序,比如我们常见的order by
和group by
(默认会对by
后面的字段进行排序)。如果出现需要排序的数据量超过了sort_buff_size
的大小或者需要排序的行数据长度超过了max_length_for_sort_data
大小,MySQL
会将这些数据放到文件中进行排序,这时候就会出现using filesort
。比如下面的案例:
explain select * from t_order where product_id>130 order by product_id;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | t_order | NULL | ALL | idx_pid | NULL | NULL | NULL | 100 | 70.00 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
如果需要放在文件中排序的话,执行效率肯定会低,一般可以通过以下方式来解决:
- 添加 limit 限制,不返还那么多数据
- 看下是否能够使用覆盖索引,大部分情况下是没必要返还全部字段的
- 修改
sort_buff_size
和max_length_for_sort_data
参数 group by
默认会按照by
后面的字段进行排序,如果不想要排序的话,可以在group by
后面加上order by null
,也能避免一些Using filesort
的产生。
比如对上面的语句添加limit
,可以看到这里已经没有filesort
了:
explain select * from t_order where product_id>130 order by product_id limit 5;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t_order | NULL | range | idx_pid | idx_pid | 4 | NULL | 70 | 100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
Using index condition
:代表可以使用索引下推,索引下推是MySQL 5.6
版本推出的新特性,其实就是把要在server
层完成的筛选,下推到存储引擎中进行筛选。减少存储引擎返回的数据。
举个栗子,比如给t_order
表加个联合索引,为了方便测试,再加个title
字段(这里表结构比较简单,不加字段可能直接走覆盖索引了):
alter table t_order add index idx_cid_pid(cid,product_id);
alter table t_order add column title varchar(12) not null default '';
执行下面的sql
发现有using index condition
:
explain select * from t_order where cid like '2000%' and product_id =10;
+----+-------------+--------------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t_order | NULL | range | idx_cid_pid | idx_cid_pid | 39 | NULL | 6 | 10.00 | Using index condition |
+----+-------------+--------------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
如果没有使用索引下推的话,首先会根据cid
在存储引擎层进行筛选,把筛选后符合cid
的数据根据主键id
进行回表查询根据product_id
字段进行筛选.
使用索引下推之后,在存储引擎层不仅会根据cid
字段筛选,还会根据product_id
字段筛选,直接就减少了需要回表的次数。
💡 出现
Using index condition
并不一定是走了索引下推,只是代表可以使用索引下推。索引下推一般是指在联合索引的情况下,根据索引中的数据可以直接做过滤。
Using temporary
:表示需要用到临时表来解决本次查询。比如上面讲到的union
出现了Using temporary
。
mysql> explain select id from t_order where product_id = 101 union select id from t_product where id = 101;
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 10.00 | Using where |
| 2 | UNION | t_product | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
单表查询也可能会出现Using temporary
:
explain select cid,product_id,title from t_order where cid = '20000001' group by cid,product_id,title order by null;
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------+------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------+------+----------+----------------------------------------+
| 1 | SIMPLE | t_order | NULL | ref | idx_cid_pid | idx_cid_pid | 35 | const | 6 | 100.00 | Using index condition; Using temporary |
+----+-------------+--------------+------------+------+---------------+-------------+---------+-------+------+----------+----------------------------------------+
这里我们可以通过把title
字段也加入到联合索引中来去掉Using temporary
:
alter table t_order add index idx_cid_pid_title(cid,product_id,title);
explain select cid,product_id,title from t_order where cid = '20000001' group by cid,product_id,title order by null;
+----+-------------+--------------+------------+------+-------------------+-------------------+---------+-------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+-------------------+-------------------+---------+-------+------+----------+--------------------------+
| 1 | SIMPLE | t_order | NULL | ref | idx_cid_pid_title | idx_cid_pid_title | 35 | const | 6 | 100.00 | Using where; Using index |
+----+-------------+--------------+------------+------+-------------------+-------------------+---------+-------+------+----------+--------------------------+
💡 索引并不是越多越好,毕竟维护索引也是需要”代价“的,我们需要做各种权衡之后再决定是否需要添加索引。
Using join buffer (Block Nested Loop)
: 出现这个代表join
连接查询时出现了嵌套循环,一般是连接的字段没有使用到索引导致的。执行下面的sql
语句,将t_order
表和t_product
表通过cid
字段进行关联:
explain select * from t_order ,t_product where t_order.cid = t_product.cid;
+----+-------------+-----------+------------+------+---------------+---------+---------+-------------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+---------+---------+-------------------------+------+----------+-------------+
| 1 | SIMPLE | t_order | NULL | ALL | idx_cid | NULL | NULL | NULL | 100 | 100.00 | NULL |
| 1 | SIMPLE | t_product | NULL | ref | idx_cid | idx_cid | 35 | seata_order.t_order.cid | 1 | 100.00 | Using index |
+----+-------------+-----------+------------+------+---------------+---------+---------+-------------------------+------+----------+-------------+
然后我们把cid
字段的索引去掉,就会发现出现了Using join buffer (Block Nested Loop)
:
explain select * from t_order ,t_product where t_order.cid = t_product.cid;
+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t_order | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | t_product | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
- Using MRR:
Multi-Range Read
(多范围读)。在我们根据非聚餐索引(二级索引)查询时,由于二级索引的顺序可能和主键索引的顺序不一致,我们拿到这些二级索引后再去回表查询就会导致很多的随机IO,MRR
就是在内存中对查出来的这些二级索引做下排序,让其按照主键索引的顺序进行排序,这样再次查询的时候就会变成顺序IO了(MySQL
是按页来读数据的,每页默认读16K)。
与MRR
相关的开关有两个分表是mrr
和mrr_cost_based
,mrr=on
代表开启MRR
,mrr_cost_based=on
表示由优化器决定是否使用MRR
,mrr_cost_based=off
表示强制使用MRR
。MySQL 5.7
对MRR
相关的默认值是:
mrr=on,mrr_cost_based=on
这里为了演示效果,我把mrr_cost_based
改为off
,强制使用MRR
:
set session optimizer_switch = 'mrr_cost_based=off';
explain select * from t_order where product_id>100 limit 10;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
| 1 | SIMPLE | t_order | NULL | range | idx_pid | idx_pid | 4 | NULL | 95 | 100.00 | Using index condition; Using MRR |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
5.小结
本文主要讲解了explain
各个字段含义,这里面字段以及字段的值还是很多的,感兴趣的小伙伴可以先收藏下,以便以后在使用explain
时有不了解的信息可以随时查看,毕竟这里可是有官网传送门的,以便你可以随时跳到官网随时查看,哈哈。
好了,这篇文章就到这里了,感谢大家的观看!如有错误,请及时指正!欢迎大家关注我的公众号:贾哇技术指南
参考
- [1] 《高性能MySQL-第二版》
- [2] MySQL官网