Mysql深度讲解 – explain关键字(一)

前言

Mysql中给我们提供了explain关键字去分析一条sql执行的大概过程。比如工作中间常常会遇到执行效率很低的”慢sql”。有些sql语句一直执行的就很慢,有些sql只是偶发性的慢。所以有时候可能sql执行得慢并不是sql的问题,而是服务器资源被占满(比如表被锁,网络问题等等),无法获取执行sql的资源导致的慢,这时候其实还好。那么如果一个sql一直运行的效率比较低,那么就必须要用到explain关键字去分析sql,本篇就是要详细的说下explain关键字。在本篇最后附录中有笔者创建的演示表,方便大家对照阅读。更多Mysql调优内容请点击【Mysql优化-深度讲解系列目录】

Explain 关键字解析

对于优化SQL一般第一步就会去查看explain输出的内容,因为通过explain的输出我们可以分析出SQL执行的步骤,从而优化SQL。下表就是explain关键字输出的字段以及说明。在解释这些关键字的同时,可能会有一些名词,比如物化表,in和exist的转换等等,这部分内容的详细解释在【Mysql深度讲解 – 子查询优化】这篇博客里基本都有涉及。

字段说明
id在一个查询语句中每个SELECT关键字都对应一个唯一的id
select_typeSELECT关键字对应的那个查询的类型
table表名
partitions匹配的分区信息
type针对单表的访问方法
possible_keys可能用到的索引
key实际上使用的索引
key_len实际使用到的索引长度
ref当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows预估的需要读取的记录条数
filtered某个表经过搜索条件过滤后剩余记录条数的百分比
Extra一些额外的信息

table字段

这个字段记录了每一个参与查询结果集的表名,比如:explain select * from t1 join t2; 这里显示的t1和t2就是参与这条查询语句的表。

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt2NULLALLNULLNULLNULLNULL3100.00NULL
1SIMPLEt1NULLALLNULLNULLNULLNULL9100.00Using join buffer (Block Nested Loop)

id字段

一般写的查询语句都以SELECT关键字开头,比较简单的查询语句里只有一个SELECT,但是有两种情况会在一条语句中出现多个SELECT。第一、WHERE中包含子查询;第二、使用UNION语句的时候。一般来说查询语句中每出现一个SELECT,Mysql就会去分配一个唯一的id。所以这个id字段是按照SELECT关键字区分是否属于一个查询语句的。


例如,普通的一条查询语句explain select * from t1

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLALLNULLNULLNULLNULL9100.00NULL

例如,带join的一条查询语句explain select * from t1 join t2,可以看到即便是两个表也被认为是一个select语句,其id都是一样的。值得注意的是在join的执行中,虽然join在一起表都会对应一条记录,而且id是相同的,但是靠前的表是驱动表,后边的表则是被驱动表。这里的结果是被查询优化器优化过执行的t2驱动t1:

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt2NULLALLNULLNULLNULLNULL3100.00NULL
1SIMPLEt1NULLALLNULLNULLNULLNULL9100.00Using join buffer (Block Nested Loop)

例如,含有In的子查询explain select * from t1 where a in (select a from t2) or c = 'c',这次查询的id就是id不同了,因为这里的两个查询是分开的。由于查询优化器可能会把in优化为join语句因此这个语句的后面加了一个or关键字。因此这里两个select就代表两次查询,所以id分配了两个:

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLNULLNULLNULLNULL9100.00Using where
2SUBQUERYt2NULLindexPRIMARYPRIMARY4NULL3100.00Using index

例如,含有Union或者 Union All的语句,UnionUnion All的区别在于Union会对查询出来的结果去重,而Union All则是原始数据,我们可以通过explain关键字查看其执行的不同之处。通过对比可以发现Union语句比Union All多了一行,这多出来的一行就是去重复的逻辑:把t1表和t2表的数据都查出来,然后放到一个临时表中,这个临时表名的字就叫做<union1,2>,字段Extra也标注了Using temporary使用临时表,Union就是基于这个临时表去重然后再返回结果出来,由于<union1,2>是一个临时表,因此它没有id。而Union All则不需要去重,因此不需要额外的步骤直接返回即可。

执行Union的查询explain select * from t1 union select * from t2

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLNULLNULLNULLNULL9100.00NULL
2UNIONt2NULLALLNULLNULLNULLNULL3100.00NULL
NULLUNION RESULT< union1,2 >NULLALLNULLNULLNULLNULLNULLNULLUsing temporary

执行执行UnionAll的查询explain select * from t1 union all select * from t2

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLNULLNULLNULLNULL9100.00NULL
2UNIONt2NULLALLNULLNULLNULLNULL3100.00NULL

select_type字段

这个字段用来表示这次查询是一个什么样的查询类型,仅仅是为了显示select进行的查询类型是什么样的,用于给用户比较直观的展示而已,没什么可以深入讲解的,它有如下几个类型。


SIMPLE 简单的查询:表示查询类型就是针对单一表的简单查询,比如:
explain select * from t1 join t2 或者explain select * from t1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt2NULLALLNULLNULLNULLNULL3100.00NULL
1SIMPLEt1NULLALLNULLNULLNULLNULL9100.00Using join buffer (Block Nested Loop)

PRIMARY 主查询:区分于子查询SUBQUERY,一般来说就是指最外面的Select查询。


SUBQUERY子查询(不相关):区分于主查询来说,一般指条件语句中的Select查询,单独出现的时候特指不相关子查询,比如:
explain select * from t1 where a in (select a from t2) or c = 'c';

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLNULLNULLNULLNULL9100.00Using where
2SUBQUERYt2NULLindexPRIMARYPRIMARY4NULL3100.00Using index

UNION 查询:和主查询平级,表示这个查询是一个UNION修饰得查询。


UNION RESULT 查询:表示这个查询是UNION查询中构建的去重临时表。
explain select * from t1 union select * from t2;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLNULLNULLNULLNULL9100.00NULL
2UNIONt2NULLALLNULLNULLNULLNULL3100.00NULL
NULLUNION RESULT< union1,2 >NULLALLNULLNULLNULLNULLNULLNULLUsing temporary

DEPENDENT SUBQUERY 相关子查询:区分于PRIMARY和SUBQUERY,一般指子查询中有数据依赖于著查询的条件查询。相关子查询如果不经过优化,会对子查询有比较多的执行次数,如果发现查询条件里有DEPENDENT SUBQUERY,可以尝试分析是否有办法优化掉。
explain select * from t1 where a in (select a from t2 where t1.a = t2.a) or c ='c';

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLNULLNULLNULLNULL9100.00Using where
2DEPENDENT SUBQUERYt2NULLeq_refPRIMARYPRIMARY4world.t1.a1100.00Using where; Using index

DERVIED 派生表:表示该查询含有用户指定的表名的表/临时表,比如:
explain select * from (select a| count(*) from t2 group by a ) as deliver_table;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARY< derived2 >NULLALLNULLNULLNULLNULL3100.00NULL
2DERIVEDt2NULLindexPRIMARYPRIMARY4NULL3100.00Using index

MATERIALIZED 物化表查询:表示该查询含有被物化处理过的表,比如下面这个查询语句,就会把in条件内的查询结果做成一个物化表。下面<subquery2>就是t2表经过物化以后的表,Mysql的查询优化器自动把t2表物化,然后和t1进行了一个join操作,比如:
explain select * from t1 where a in (select c from t2 where e = 1);

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLE< subquery2 >NULLALLNULLNULLNULLNULLNULL100.00Using where
1SIMPLEt1NULLeq_refPRIMARYPRIMARY4< subquery2 >.c1100.00NULL
2MATERIALIZEDt2NULLALLNULLNULLNULLNULL333.33Using where

type字段

这个字段表示访问方法,比如一个查询是全表扫描ALL,还是索引index,或者部分查询range等等查询的类型,就是通过这个字段显示的。


system:在MyISAM这种精确存储引擎中使用,且当表里仅有一行数据时使用,不多说。


const:表示常量查询。针对查询使用了主键索引或者唯一索引,并且该查询有等值常量条件的时候会使用的类型,比如:
explain select * from t1 where a = 1 或者 select * from t1 where a in(1);

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLconstPRIMARYPRIMARY4const1100.00NULL

eq_ref:一般出现在join查询中,表示被驱动表,比如。
explain select * from t1 join t2 on t1.a = t2.a;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt2NULLALLPRIMARYNULLNULLNULL3100.00NULL
1SIMPLEt1NULLeq_refPRIMARYPRIMARY4world.t2.a1100.00NULL

ref:一般出现在辅助索引查询中。当一个查询使用的不是主键索引,而是使用的辅助索引会使用该类型,比如:
explain select * from t1 where b = 1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLrefidx_t1_bcdidx_t1_bcd5const2100.00NULL

ref_or_null:查询语句除了ref的条件以外还是用了is null条件,比如:
explain select * from t1 where b = 1 or b is null;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLref_or_nullidx_t1_bcdidx_t1_bcd5const3100.00Using index condition

index_merge:索引合并。被Mysql判断为索引可以合并的情况,比如下面的sql语句。这个语句的条件a=1可以用到主键索引,b=1可以用到辅助索引。因此a=1拿到结果集1,b=1拿到结果集2。由于使用的是or连接,因此将结果集1和结果集2合并即可,可以看作两个索引同时再用,所以被称为index_merge
explain select * from t1 where a = 1 or b = 1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLindex_mergePRIMARY,idx_t1_bcdidx_t1_bcd,PRIMARY5,4NULL3100.00Using sort_union(idx_t1_bcd,PRIMARY); Using where

unique_subquery:唯一子查询。这个条件出现的情况是:如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行的类型就是unique_subquery,比如:
explain select * from t1 where c in (select a from t2 where t1.e = t2.e) or a = 1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLPRIMARYNULLNULLNULL9100.00Using where
2DEPENDENT SUBQUERYt2NULLunique_subqueryPRIMARYPRIMARY4func133.33Using where

index_subquery:索引子查询。这个条件和上面的类似,只不过访问子查询中的表时使用的时普通的索引,比如:
explain select * from t1 where c in (select b from t1) or a = 1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYt1NULLALLPRIMARYNULLNULLNULL9100.00Using where
2DEPENDENT SUBQUERYt2NULLunique_subqueryPRIMARYPRIMARY4func133.33Using where

range:范围查询。一般出现在<、>、in条件比较少的情况,表示使用的是一个范围。比如:
explain select * from t1 where a > 1 或者 select * from t1 where a in( 1|2);

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLrangePRIMARYPRIMARY4NULL8100.00Using where

index:索引查询。表示使用索引的查询,对于建了索引的列来说,最快的就是使用索引扫描,因此即便是没有where条件,也不符合最左原则,依然会使用索引。只不过如果是不适用索引的条件进行扫描的时候,会扫描整个索引。该过程类似于全表扫描,但是由于索引比全表扫描数据少,所以依然比全表扫描成本少,因此也会使用索引。注:由于不会进行where比较,因此不会走索引非叶子节点,比如:
explain select c from t1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLindexNULLidx_t1_bcd15NULL9100.00Using index

All:全表扫描。对于某些不是主键,且没有索引的列进行的查找,比如。
explain select e from t1;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEt1NULLALLNULLNULLNULLNULL9100.00NULL

type字段的性能排序

对于这么多的查询类型,就有了性能问题了。type的查询性能就是本篇对type字段的介绍顺序,从上往下依次减少。

字段名解释
const常量自然是最快的,第一。
eq_ref代表使用的是主键或者唯一索引,第二。
ref代表使用的普通索引,第三。
ref_or_null代表使用的普通索引且使用了is null条件,第四。
index_merge代表使用索引合并,已经至少使用两个索引了,第五。
unique_subquery子查询使用唯一索引,第六。
index_subquery子查询使用辅助索引,第七。
range范围查询慢于等值查询,第八。
index使用索引进行全表查询,扫描整个索引树,第九。
ALL全表扫描,不使用索引,最慢的,第十。

所以性能排序从大到小就是:
system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

总结

本篇中笔者对explain中的关键字table、id、select_type、type等四个字段做了详细的解释,并将其出现条件做了归类整理,下篇【Mysql深度讲解 – explain关键字(二)】将会继续对剩余字段进行解析。

附:本例中构建的表:

create table t1(
a int primary key,
b int,
c int,
d int,
e varchar(20)
) engine=InnoDB;
create index idx_t1_bcd on t1(b,c,d);

insert into t1 values(12,1,2,4,'a');
insert into t1 values(6,4,5,4,'b');
insert into t1 values(9,1,1,1,'c');
insert into t1 values(1,6,7,4,'d');
insert into t1 values(15,2,2,5,'e');
insert into t1 values(7,9,3,6,'f');
insert into t1 values(4,2,1,7,'g');
insert into t1 values(3,3,3,3,'h');
insert into t1 values(10,5,5,5,'ss');

create table t2(
a int primary key,
b int,
c int,
d int,
e varchar(20)
) engine=InnoDB;

insert into t2 values(1,6,7,4,'d');
insert into t2 values(4,2,1,7,'g');
insert into t2 values(3,3,3,3,'h');
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值