MySQL查询优化之explain 执行计划 深入解析(精品)

相关文章

标题 链接
SQL优化 实战 >>点击查看<<正在编写ing

一. 前言

在日常开发过程中,由于时间紧迫,项目组成员水平不一,会导致很多的SQL执行很慢. 此时我们一般会使用Explain 命令来查看这些SQL语句的执行计划,来分析SQL的执行过程.查看该SQL语句有没有使用上索引,有没有做全表扫描 等等. 我们深入的了解MySQl基于开销的优化器,还可以获得很多可能被优化器考虑到的访问策略的细节,以及当运行SQL语句时哪种策略预计会被优化器采用。

使用Explain关键字可以模拟优化器执行SQL语句,分析查询语句或是结构的性能瓶颈。在select语句之前增加explaion关键字,MySQL会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是执行SQL。

二. Explain 用途

  1. 表的读取顺序如何
  2. 数据读取操作有哪些操作类型
  3. 哪些索引可以使用
  4. 哪些索引被实际使用
  5. 表之间是如何引用
  6. 每张表有多少行被优化器查询
    … …

三. Explain语法

explain SQL语句
例:

explain select * from user

四. 执行效果

4.1. 命令行

mysql> explain select * from userInfo where id = 1 \G
******************************************************
                      id: 1
      select_type: SIMPLE
                table: userInfo
         partitions: NULL
                type: const
possible_keys: PRIMARY
                 key: PRIMARY
          key_len: 4
                 ref: const
              rows: 1
          filtered: 100.00
            Extra: NULL
******************************************************

4.2. 可视化工具–HeidiSQL Portable 9.4

explain select * from user

在这里插入图片描述

五. Explain 字段详解

5.1. 一览全局字段

字段 解释
id select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
select_type 查询类型
tab 正在访问哪个表
partitions 匹配的分区
type 访问的类型
possible_keys 显示可能应用在这张表中的索引,一个或多个,但不一定实际使用到
key 实际使用到的索引,如果为NULL,则没有使用索引
key_len 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
ref 显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值
rows 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数
filtered 查询的表行占表的百分比
Extra 包含不适合在其它列中显示但十分重要的额外信息

在这里插入图片描述

5.2. Id字段

  1. id列的编号是select的序列号,有几个select就有几个id,并且id的顺序是按select出现的顺序增长的。
  2. id越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。

5.2.1. id相同

说明:
  从上至下 顺序执行
脚本:

explain 
select t1.* , t2.* , t3.*
from actor t1 , film t2 , film_actor t3
where t3.film_id = t2.id and t3.actor_id = t1.id

执行结果:
在这里插入图片描述
解说:
  如图所示,ID列的值全为1,代表执行顺序从t1表开始加载,依次为t3,t2

5.2.2. id不相同

说明:
  如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
脚本:

explain 
select t1.*
from actor t1
where t1.id = (select t2.actor_id from film_actor t2 where t2.id=3)

执行结果:
在这里插入图片描述
解说:
  如上图所示 , t2表的id值为2 , 所以执行顺序将先从t2表开始加载,然后再执行t1表

5.2.3. id相同,又不相同

说明:

  1. 将id不相同的部分可以分为一组, id值越大,越限执行
  2. id 相同的一组 , 从上至下顺序执行

脚本:

explain
select t1.* from actor t1 left join film_actor t2 on t1.id = t2.actor_id
union
select t1.* from actor t1 right join film_actor t2 on t1.id = t2.actor_id

执行结果:
在这里插入图片描述
解说:

  1. id如果相同,可以认为是一组,从上往下顺序执行;
  2. 在所有组中,id值越大,优先级越高,越先执行

5.3. select_type字段

Select_type:查询的类型,
要是用于区别:普通查询、联合查询、子查询等的复杂查询

5.3.1.类型如下

类型 描述
SIMPLE 简单的select查询中不好喊子查询或者UNION
PRIMARY 查询中若包含任何复杂的子部分,最外层查询则被标记为
SUBQUERY 在SELECT或WHERE列表中包含了子查询
DERIVED(衍生) 在FROM列表中包含的子查询被标记为DERIVED(衍生). MySQL会递归执行这些子查询, 把结果放在临时表里。
UNION 若第二个SELECT出现在UNION之后,则被标记为UNION
UNION RESULT 从UNION表获取结果的SELECT

5.3.2. SIMPLE

说明:
  简单的select查询中不好喊子查询或者UNION
脚本:

explain 
select t1.* from actor t1 ,film_actor t2 , film t3
where t1.id = t2.actor_id and t3.id = t2.film_id

执行结果:
在这里插入图片描述

5.3.2. PRIMARYSUBQUERY

说明:
   PRIMARY: 查询中若包含任何复杂的子部分,最外层查询则被标记为主查询
   SUBQUERY: 在SELECT或WHERE列表中包含了子查询
脚本:

explain 
select t1.*
from actor t1
where t1.id = (select t2.actor_id from film_actor t2 where t2.id=3)

执行结果:
在这里插入图片描述

5.3.3. DERIVED

说明:
  在FROM列表中包含的子查询被标记为DERIVED(衍生)
  MySQL会递归执行这些子查询, 把结果放在临时表里。

MySQL5.7+ 进行优化了,增加了derived_merge(派生合并),默认开启,可加快查询效率

5.3.4. UNION RESULTUNION

说明:
  UNION:若第二个SELECT出现在UNION之后,则被标记为UNION
  UNION RESULT:从UNION表获取结果的SELECT
脚本:

explain
select t1.* from actor t1 left join film_actor t2 on t1.id = t2.actor_id
union
select t1.* from actor t1 right join film_actor t2 on t1.id = t2.actor_id

执行结果:
在这里插入图片描述

5.4. table字段

  显示这一行的数据是关于哪张表的
脚本:

explain
select * from actor  

执行结果:
在这里插入图片描述

5.5. type

 type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

需要记忆的
  NULL>system>const>eq_ref>ref>range>index>ALL

 一般来说,得保证查询至少达到range级别,最好能达到ref。

5.5.1. Systemconst

说明:
  System:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计
  const:表示通过索引一次就找到了. const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快. 如将主键置于where列表中,MySQL就能将该查询转换为一个常量
脚本:

explain
select * from (select * from actor t1 where t1.id = 1) s1 ;

执行结果:
在这里插入图片描述

5.5.2. eq_ref

说明:
   唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
脚本:

explain
select * from actor t1 left join film_actor t2 on t2.id =  t1.id ;

执行结果:
在这里插入图片描述

5.5.3. ref

说明:
   非唯一性索引扫描,返回匹配某个单独值的所有行. 本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体

表示的是联合索引.

脚本:

explain
select count(distinct actor_id) from film_actor t1 where t1.actor_id = 1

执行结果:
在这里插入图片描述

5.5.4. Range

说明:

  1. 只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引
  2. 一般就是在你的where语句中出现了between、<、>、in等的查询
  3. 这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。

脚本:

explain
select t1.* from actor t1 where t1.id between 1 and 3

执行结果:
在这里插入图片描述

5.6. possible_keyskey

possible_keys:可能使用的key
Key:实际使用的索引。如果为NULL,则没有使用索引

查询中若使用了覆盖索引,则该索引和查询的select字段重叠
这里的覆盖索引非常重要,后面会单独的来讲

解说:

其中key和possible_keys都可以出现null的情况(结婚邀请朋友的例子)

  1. 结婚时,邀请了自己认为回来的朋友—> possible_keys
  2. 实际到场的朋友---->key

5.7. key_len

说明:
Key_len表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的

注意:
更具底层使用的不同存储引擎,受影响的行数这个指标可能是一个估计值,也可能是一个精确值. 即使受影响的行数是一个估计值(例如 当使用InnoDB存储引擎管理表存储时),通常情况下这个估计只是一个足以使优化器租出一个有充分依据 的决定

  • key_len表示索引使用的字节数,
  • 根据这个值,就可以判断索引使用情况,特别是在组合索引的时候,判断所有的索引字段是否都被查询用到。
  • char和varchar跟字符编码也有密切的联系,
  • latin1占用1个字节,gbk占用2个字节,utf8占用3个字节。(不同字符编码占用的存储空间不同)

5.7.1 字符类型

在这里插入图片描述

以上这个表列出了所有字符类型,但真正建所有的类型常用情况只是CHAR、VARCHAR

5.7.2 字符类型-索引字段为char类型+不可为Null时

脚本:

CREATE TABLE `s1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(10) NOT NULL,
  `addr` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

explain select * from s1 where name='enjoy';

执行结果:
在这里插入图片描述
解说:

name这一列为char(10),字符集为utf-8占用3个字节
Keylen=10*3

5.7.3 字符类型-索引字段为char类型+允许为Null时

脚本:

CREATE TABLE `s2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(10) DEFAULT NULL,
  `addr` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
explain select * from s2 where name='enjoyedu';

执行结果:
在这里插入图片描述
解说:

name这一列为char(10),字符集为utf-8占用3个字节,外加需要存入一个null值
Keylen=10*3+1(null) 结果为31
结果允许为null时,需要+1

5.7.4 索引字段为varchar类型+不可为Null时

脚本:

CREATE TABLE `s3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL,
  `addr` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
explain select * from s3 where name='enjoyeud';

执行结果:
在这里插入图片描述

解说:

Keylen=varchar(n)变长字段+不允许Null=n*(utf8=3,gbk=2,latin1=1)+2

5.7.5 索引字段为varchar类型+允许为Null时

脚本:

CREATE TABLE `s4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL,
  `addr` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
explain select * from s4 where name='enjoyeud';

执行结果:
在这里插入图片描述

解说:

Keylen=varchar(n)变长字段+允许Null=n*(utf8=3,gbk=2,latin1=1)+1(NULL)+2

5.7.6 数值类型

在这里插入图片描述

CREATE TABLE `numberKeyLen ` (
`c0`  int(255) NOT NULL ,
`c1`  tinyint(255) NULL DEFAULT NULL ,
`c2`  smallint(255) NULL DEFAULT NULL ,
`c3`  mediumint(255) NULL DEFAULT NULL ,
`c4`  int(255) NULL DEFAULT NULL ,
`c5`  bigint(255) NULL DEFAULT NULL ,
`c6`  float(255,0) NULL DEFAULT NULL ,
`c7`  double(255,0) NULL DEFAULT NULL ,
PRIMARY KEY (`c0`),
INDEX `index_tinyint` (`c1`) USING BTREE ,
INDEX `index_smallint` (`c2`) USING BTREE ,
INDEX `index_mediumint` (`c3`) USING BTREE ,
INDEX `index_int` (`c4`) USING BTREE ,
INDEX `index_bigint` (`c5`) USING BTREE ,
INDEX `index_float` (`c6`) USING BTREE ,
INDEX `index_double` (`c7`) USING BTREE 
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;


EXPLAIN
select * from  numberKeyLen where c1=1

EXPLAIN
select * from  numberKeyLen where c2=1

EXPLAIN
select * from  numberKeyLen where c3=1


EXPLAIN
select * from  numberKeyLen where c4=1

EXPLAIN
select * from  numberKeyLen where c5=1

EXPLAIN
select * from  numberKeyLen where c6=1

EXPLAIN
select * from  numberKeyLen where c7=1

5.7.7 日期和时间

在这里插入图片描述
datetime类型在5.6中字段长度是5个字节
datetime类型在5.5中字段长度是8个字节

CREATE TABLE `datatimekeylen ` (
`c1`  date NULL DEFAULT NULL ,
`c2`  time NULL DEFAULT NULL ,
`c3`  year NULL DEFAULT NULL ,
`c4`  datetime NULL DEFAULT NULL ,
`c5`  timestamp NULL DEFAULT NULL ,
INDEX `index_date` (`c1`) USING BTREE ,
INDEX `index_time` (`c2`) USING BTREE ,
INDEX `index_year` (`c3`) USING BTREE ,
INDEX `index_datetime` (`c4`) USING BTREE ,
INDEX `index_timestamp` (`c5`) USING BTREE 
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;


EXPLAIN
SELECT  * from datatimekeylen where c1 = 1

EXPLAIN
SELECT  * from datatimekeylen where c2 = 1

EXPLAIN
SELECT  * from datatimekeylen where c3 = 1

EXPLAIN
SELECT  * from datatimekeylen where c4 = 1

EXPLAIN
SELECT  * from datatimekeylen where c5 = 1

5.7.8 总结

5.7.8.1 字符类型

  1. 变长字段需要额外的2个字节(VARCHAR值保存时只保存需要的字符数,另加一个字节来记录长度(如果列声明的长度超过255,则使用两个字节),所以VARCAHR索引长度计算时候要加2),固定长度字段不需要额外的字节。
  2. 而NULL都需要1个字节的额外空间,所以索引字段最好不要为NULL,因为NULL让统计更加复杂并且需要额外的存储空间。
  3. 复合索引有最左前缀的特性,如果复合索引能全部使用上,则是复合索引字段的索引长度之和,这也可以用来判定复合索引是否部分使用,还是全部使用。

5.6.7.2 整数/浮点数/时间类型的索引长度

NOT NULL=字段本身的字段长度
NULL=字段本身的字段长度+1(因为需要有是否为空的标记,这个标记需要占用1个字节)

datetime类型在5.6中字段长度是5个字节,datetime类型在5.5中字段长度是8个字节

5.8 Ref

说明:
   显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
脚本:

explain 
select * from actor t1 , film_actor t2 where t1.id = t2.actor_id and t2.film_id=2

执行结果:
在这里插入图片描述
解说:
  由key_len可知t1表的actor_id被充分使用,
  actor_id匹配t2表的idfilm_id匹配了一个常量,即 1

   其中 【mysql_explain.t2.actor_id】 为 【数据库.表.列】

5.8 Rows

说明:
   根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
脚本:

explain 
select * from actor t1 , film_actor t2 where t1.id = t2.actor_id and t2.film_id=2

执行结果:
在这里插入图片描述

5.9 Extra

 包含不适合在其它列中显示但十分重要的额外信息

5.9.1 选项描述

描述
Using filesort 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为"文件排序"
Using temporary 使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
Using index 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!   如果同时出现using where,表明索引被用来执行索引键值的查找;   如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
Using where 使用了where条件
Using join buffer 使用了连接缓存
impossible where where子句的值总是false,不能用来获取任何元素
distinct 一单mysql找到了与形相联合匹配的行,就不在搜索了

说明:

  1. 由于Markdown 编辑格式问题, 描述不是很全
  2. 在下面各个值说明中,比较完整

5.9.2 Using filesort

说明:
  说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
  MySQL中无法利用索引完成的排序操作称为“文件排序”

当发现有Using filesort 后,实际上就是发现了可以优化的地方

脚本:

explain 
select * from film_actor t1 where t1.film_id=1 order by t1.actor_id

执行结果:
在这里插入图片描述
解说:
  上图其实是一种索引失效的情况,后面会讲,可以看出查询中用到了个联合索引,索引分别为actor_id,film_id

5.9.3 Using temporary

说明:
   使用了临时表保存中间结果,MySQL在对结果排序时使用临时表,常见于排序order by 和分组查询group by
脚本:

explain 
select * from film_actor t1 where t1.film_id=1 group by t1.remark

执行结果:
在这里插入图片描述
解说:
  尤其发现在执行计划里面有using filesort而且还有Using temporary的时候,特别需要注意

5.9.3 Using index

说明:
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找
脚本:

explain 
select * from film t1 where t1.name="我的快乐购"

执行结果:
在这里插入图片描述
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
  
脚本:

explain 
select * from film t1

执行结果:
在这里插入图片描述

5.9.3.1 覆盖索引

  1. 覆盖索引(Covering Index),一说为索引覆盖。
  2. 理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
  3. 理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引

注意:
 如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,
因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

 所以,千万不能为了查询而在所有列上都建立索引,会严重影响修改维护的性能。

5.9.4 Using whereusing join buffer

说明:
   Using where:表明使用where过滤
   using join buffer:使用了连接缓存

很容易理解,不做案例演示

脚本:

-- 查询MySQl 默认的join_buffer_size
show VARIABLES like '%join_buffer_size%'

执行结果:
在这里插入图片描述

5.9.5 impossible where

说明:
   where子句的值总是false,不能用来获取任何元组
脚本:

-- where 条件的字句总是为false
explain 
select * from film t1 where 1=2

-- 
explain 
select * from film t1 where t1.name="随机填入" and t1.name="随机填入"

执行结果:
在这里插入图片描述

### 5.9.6 `distinct`

说明:
   一旦mysql找到了与行相联合匹配的行,就不再搜索了
脚本:

explain 
select  distinct  t1.id from actor t1 ,film_actor t2 , film t3 where t1.id = t2.actor_id and t2.film_id = t3.id

执行结果:
在这里插入图片描述
解说:
  

5.9.7 Select tables optimized away

说明:
   SELECT操作已经优化到不能再优化了(MySQL根本没有遍历表或索引就返回数据了)
脚本:

explain 
select  min(t1.id) from actor t1 

执行结果:
在这里插入图片描述


案例脚本

1. 建表脚本


-- 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

-- film建表语句:
CREATE TABLE `film` (
  `id` int(11) NOT NULL,
  `name` varchar(10) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 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`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2. 插入数据sql


-- actor 表
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (3, '丽丽', '2020-02-28 22:50:57');
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (1, '媛媛', '2020-02-28 22:49:53');
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (2, '锐锐', '2020-02-28 22:50:44');

-- film 表
INSERT INTO `film` (`id`, `name`) VALUES (3, '授课老师看到');
INSERT INTO `film` (`id`, `name`) VALUES (1, '我的快乐购');
INSERT INTO `film` (`id`, `name`) VALUES (2, '哈哈杜拉拉');


-- film_actor 表
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (5, 3, 2, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (6, 3, 2, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (2, 2, 1, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (4, 2, 3, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (1, 1, 1, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (3, 1, 3, NULL);


发布了72 篇原创文章 · 获赞 78 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/Dreamhai/article/details/104558854