mysql

详解mysql的EXPLAIN命令

2020-08-04  本文已影响0人  今年五年级

一 简介

使用explain命令可以模拟优化器执行sql语句,从而知道mysql是如何处理我们写的sql语句的,还可以分析你的查询语句或者是结构的性能瓶颈

二 注意事项

在select语句之前增加explain关键字以后,mysql会在查询上设置一个标记,执行查询时,会返回执行计划的信息,而不是执行这条sql(如果from中包含子查询,则仍会执行该子查询,将结果放入临时表中)

三 数据准备

创建如下3个表,演员,电影,演员电影中间表
电影表对电影名称name字段创建普通索引
演员表对电影id和演员id联合辅助索引,非联合主键索引

DROP TABLE IF EXISTS `actor`;
CREATE TABLE `actor` (
  `id` int(11) NOT NULL,
  `name` varchar(45) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (1,'a','2017-12-22 15:27:18'), (2,'b','2017-12-22 15:27:18'), (3,'c','2017-12-22 15:27:18');

DROP TABLE IF EXISTS `film`;
CREATE TABLE `film` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)    // 电影名称索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `film` (`id`, `name`) VALUES (3,'film0'),(1,'film1'),(2,'film2');

DROP TABLE IF EXISTS `film_actor`;
CREATE TABLE `film_actor` (
  `id` int(11) NOT NULL,
  `film_id` int(11) NOT NULL,
  `actor_id` int(11) NOT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_film_actor_id` (`film_id`,`actor_id`)     //电影id和演员id联合辅助索引,非联合主键索引!!!
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`) VALUES (1,1,1),(2,1,2),(3,2,1); 

四 explain结果每一列解析

4.1 id列

id列的编号是select的序列号,有几个select就会有几个id,并且id的顺序是按照select出现的顺序来顺序增长的,mysql将select查询分为简单查询(SIMPLE)和复杂查询(PRIMARY)
复杂查询分为三类

(1)简单子查询

explain select( select 1 from actor limit 1) from file

(2)from字句中的子查询

explain select id from (select id from film) as der

查询执行时有临时表别名为der,外部select查询引用了该临时表


(3)union查询
见4.2节union案例,union实际用的不太多

union结果总是放在一个匿名临时表中,临时表不在SQL中出现,因此它的id是NULL

4.2 select_type列

select_type 表示对应行是简单还是复杂的查询,如果是复杂的查询,又是上述三种复杂查询中的哪一种

explain select * from film where id = 1
explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der

DERIVED:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为衍生表/派生表(derived的英文含义)
SUBQUERY:包含在 select 中的子查询(不在 from 子句中)
PRIMARY:复杂查询中最外层的 select,使用了id为3的查询结果衍生出来的表

explain select 1 union all select 1

union:在 union 中的第二个和随后的 select
union result:从 union 临时表检索结果的 select

4.3 table列

改行表示explain的一行sql正在访问哪个表
当from子句中有子查询时,table列是<derivedN>格式,表示当前查询依赖id=N的查询,因此先执行id=N的查询
当有union时,union result的table列的值为<union1,2>,1和2表示参与union的select的id

4.4 type列(重要)

关联类型,表示mysql将如何找到查找表中的行,查询数据行的大概范围
结果从最好到最坏依次是
system>const>eq_ref>ref>range>index>ALL
一般需要保证查询达到range级别,最好达到ref

(1)system

表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计

explain select * from (select * from film where id = 1) tmp;
(2)const

表的查询结果只有一行,一般出现在主键索引和唯一索引上,因为是唯一,所以读取1次,速度比较快

select * from film where id = 1
(3)eq_ref(const之外最好的性能)

表连接的时候,关联的b表的主键索引或者唯一索引,b表根据主键索引或者唯一索引最多返回一条记录,这样关联的效率高

Mysql执行计划中eq_ref和ref类型的区别
MySQL解释了eq_ref和ref类型的含义
这个JOIN非常快,因为对于表A中扫描的每一行,表B中只能有一行满足JOIN条件。一个,不超过一个。那是因为B.id是独一无二的。在这里,您是一个伪代码,它说明了服务器端的处理:

foreach (rowA in A)
{
   if (existsInBRowWithID(rowA.id)
   {
       addToResult(rowA.text, getRowInBWithID(rowA.id).text);
       break;
   }
}
explain select * from film_actor a left join film b on a.film_id=b.id
(4)ref

表连接的时候,关联的b表采用普通辅助索引(非唯一索引)或者唯一索引的部分前缀,一般都是用在多表连接上的,关联的字段不是主键索引或者唯一索引 即a left join b on a.filmname=b.name

此JOIN不如前一个er_ref快,因为对于表A中扫描的每一行,表C中有几个可能的行,它们可以满足JOIN条件(上面的循环中没有中断)。那是因为C.ID不是独一无二的

foreach (rowA in A)
{
   foreach (rowC in C)
   {
       if (rowA.id == rowC.id)
       {
           addToResult(rowA.text, rowC.text);
       }
   }
}
(4.1)简单 select 查询,name是普通索引(非唯一索引)
explain select * from film where name='film1'

使用唯一索引(
索引名:idx_film_actor_id |
构成: file_id和actor_id
)的部分前缀film_id

explain select * from film_actor where film_id=1
explain select * from film_actor where actor_id=1
(4.2)关联表查询

idx_film_actor_id是film_id和actor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分

explain select film_id from film left join film_actor on film.id = film_actor.film_id;
eq_ref 和 ref的核心对比

A表的基础:A表的非主键字段
A表的外键关联了B表的主键
A表的外键关联了B表的非主键字段,B表对应A表外键字段的记录有多条

(5)range

范围扫描通常出现在范围查找中,比如in(),between ,>, <, >= ,<=等操作中,使用一个索引来检索给定的行

explain select * from actor where id > 1

之所以范围查找速度慢,原因一:查询出的记录多,原因二:比如上面的id>1,除了第一条记录id=1,其他记录都大于1,等于进行了全表扫描

(6)index

会扫描全表索引,通常比ALL速度快,原因是(select * ,* 是id和name,通过name辅助索引树找到对应name的记录,然后获取主键索引id,都是在索引树上查询)index是在索引中读取所有记录(可以设置把索引都加载到内存中),而ALL是在硬盘上读取

explain select * from film; 

film表2个字段,id字段为主键索引,name字段为普通辅助索引,要想是index,则必须表的所有字段都有索引

(7)ALL

不会走索引,顺序从第一行到最后一行查找所需要的行,全表扫描,这种情况需要添加索引优化

explain select * from actor; 

之所以这次是ALL不是index,主要是因为actor表3个字段,id,name,update_time只有id有主键索引,其他两个字段上都没索引,所以无法采用全表索引查询
尝试只查询id,name

EXPLAIN select id,name from actor

为name字段加上索引再执行

4.5 possible_keys列

显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

4.6 key列

意思是这次查询实际使用哪个索引来提高查询性能,如果没有使用索引,该列为null没如果想让mysql强制使用possible_keys列中的索引,可以使用force index

可能出现possible_keys有值,而key为null的情况,这种情况一般发生在表中记录不多,mysql认为使用索引对查询帮助不大,因此选择了全表扫描

4.7 key_len列

本列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列
比如,file_actor表联合索引idx_file_actor_id由 film_id 和 actor_id 两个int列组成,并且每个int是4个字节

explain select * from film_actor where film_id = 2;

通过上图中结果中的key_len=4可推断出查询使用了联合索引的第一个列file_id列
如果加上另一个字段

explain select * from film_actor where film_id = 2;

可以看出这次用了联合索引的全部字段

附注:key_len计算规则:

索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引

4.8 ref列

ref列展示的就是与索引列作等值匹配的东东是个啥,比如只是一个常数或者是某个列
由key_len可知,t1表的idx_col_col2被充分利用,col1匹配t2表的col1,col2匹配了一个常数

常见的有:const(常量),字段名(例:film.id)

explain select * from film_actor a left join film b on a.film_id=b.id

用到了b表的主键索引,b表主键索引相比较的是a表的film_id,study是数据库名称

4.9 rows列

根据表统计信息及索引选用情况,mysql大致估算出要读取并检测的行数,该行数不是结果集中的行数
查询优化器全表扫描的时候,代表的是预计需要扫描的行
如果使用索引来查询是,代表的是预计扫描的索引记录的行数。

4.10 extra列

包含不适合在其他列中显示但十分重要的额外信息
详细解读

explain select film_id from film_actor where film_id=1
explain select film_id from film_actor where actor_id=1
explain select remark from film_actor where actor_id=1
explain select * from actor where name='a'

上面的这个* 既包含了有索引的列id,还包含了没有索引的name和updatetime,因此查询的列未完全被索引覆盖,且where使用的条件name也没索引,优化的话:直接name创建索引,不用select * 改用select name

explain select * from film_actor where film_id = 1;  //或者* 换成remark
explain select * from film_actor where film_id>1  // 有问题,最后是using where
explain select distinct name from actor;

film表的name有索引,因此直接用索引查找,并未使用到临时表

explain select distinct name from film
上一篇 下一篇

猜你喜欢

热点阅读