执行计划

执行计划:

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的,分析并找出查询语句或是表结构的性能瓶颈。

语法:

Explain SQL语句

执行计划的作用:

  • 表的读取顺序;
  • 数据读取操作的操作类型;
  • 哪些索引可以使用;
  • 哪些索引被实际使用;
  • 表之间的引用;
  • 每张表有多少行被优化器查询。

执行计划包含的信息:

  • id;
  • select_type;
  • table;
  • type;
  • possible_keys;
  • key;
  • key_len;
  • ref;
  • rows;
  • extra。

执行计划-id

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。

三种情况:

  • id相同:执行顺序由上至下;
  • id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行;
  • id相同不同:同时存在。
id相同

![image-20210103095812375](2020-12-30 02-执行计划.assets/image-20210103095812375.png)

id不同

![image-20210103095831263](2020-12-30 02-执行计划.assets/image-20210103095831263.png)

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

id相同又不同

![image-20210103095913563](2020-12-30 02-执行计划.assets/image-20210103095913563.png)

  1. id如果相同,可以认为是一组,从上往下顺序执行;

  2. 在所有组中,id值越大,优先级越高,越先执行。

执行计划-select_type

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

具体包括:

  • SIMPLE;
  • PRIMARY;
  • SUBQUERY;
  • DERIVED;
  • UNION;
  • UNION RESULT。

#SIMPLE
EXPLAIN
select * from t1

#PRIMARY SUBQUERY
EXPLAIN
select t1.*,(select t2.id from t2 where t2.id = 1 ) from t1

#DERIVED
EXPLAIN
select t1.* from t1 ,(select t2.* from t2 where t2.id = 1 ) s2 where t1.id = s2.id

#UNION RESULT,UNION
EXPLAIN
select * from t1
UNION
select * from t2

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

执行计划-table

显示这一行的数据是关于哪张表的。

![image-20210103100345945](2020-12-30 02-执行计划.assets/image-20210103100345945.png)

执行计划-type

type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

其中比较重要的是:

system > const > eq_ref > ref > range > index > ALL

system

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

const

表示通过索引一次就找到了。

const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。

比如,将主键置于where列表中,MySQL就能将该查询转换为一个常量。

![image-20210103100715941](2020-12-30 02-执行计划.assets/image-20210103100715941.png)

EXPLAIN
SELECT * from (select * from t2 where id = 1) t1;

eq_ref

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

![image-20210103100851610](2020-12-30 02-执行计划.assets/image-20210103100851610.png)

EXPLAIN
SELECT * from t1,t2 where t1.id = t2.id;

ref

非唯一性索引扫描,返回匹配某个单独值的所有行。

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

![image-20210103101056914](2020-12-30 02-执行计划.assets/image-20210103101056914.png)

range

只检索给定范围的行,使用一个索引来选择行。

key列显示使用了哪个索引。

一般就是在where语句中出现了between、<、>、in等的查询。

这种范围索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。

![image-20210103101407340](2020-12-30 02-执行计划.assets/image-20210103101407340.png)

![image-20210103101416149](2020-12-30 02-执行计划.assets/image-20210103101416149.png)

all

Full Table Scan,将遍历全表以找到匹配的行。

![image-20210103101503308](2020-12-30 02-执行计划.assets/image-20210103101503308.png)

执行计划-possible_keys

实际使用的索引。

如果为NULL,则没有使用索引。

查询中若使用了覆盖索引,则该索引和查询的select字段重叠。

![image-20210103101703011](2020-12-30 02-执行计划.assets/image-20210103101703011.png)

![image-20210103101713613](2020-12-30 02-执行计划.assets/image-20210103101713613.png)

执行计划-key_len

![image-20210103101835552](2020-12-30 02-执行计划.assets/image-20210103101835552.png)

![image-20210103101847436](2020-12-30 02-执行计划.assets/image-20210103101847436.png)

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,根据这个值,就可以判断索引使用情况,特别是在组合索引的时候,判断所有的索引字段是否都被查询用到。

在不损失精确性的情况下,长度越短越好。

key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内数据长度检索出的。

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

char和varchar跟字符编码也有密切的联系,不同字符编码占用的存储空间不同,latin1占用1个字节,gbk占用2个字节,utf8占用3个字节。

字符类型:

![image-20210103102200942](2020-12-30 02-执行计划.assets/image-20210103102200942.png)

索引字段为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’;

![image-20210103102234498](2020-12-30 02-执行计划.assets/image-20210103102234498.png)

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

索引字段为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’;

![image-20210103102255523](2020-12-30 02-执行计划.assets/image-20210103102255523.png)

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

索引字段为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’;

![image-20210103102852168](2020-12-30 02-执行计划.assets/image-20210103102852168.png)

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

索引字段为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’;

![image-20210103103649478](2020-12-30 02-执行计划.assets/image-20210103103649478.png)

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

数值类型:

![image-20210103103804050](2020-12-30 02-执行计划.assets/image-20210103103804050.png)

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;

日期和时间:

![image-20210103103944082](2020-12-30 02-执行计划.assets/image-20210103103944082.png)

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;

整数/浮点数/时间类型的索引长度总结:
  • NOT NULL=字段本身的字段长度;
  • NULL=字段本身的字段长度+1(因为需要有是否为空的标记,这个标记需要占用1个字节);
  • datetime类型在5.6中字段长度是5个字节,datetime类型在5.5中字段长度是8个字节。
key_len总结:
  • 变长字段需要额外的2个字节(VARCHAR值保存时只保存需要的字符数,另加一个字节来记录长度(如果列声明的长度超过255,则使用两个字节),所以VARCAHR索引长度计算时候要加2),固定长度字段不需要额外的字节。

  • 而NULL都需要1个字节的额外空间,所以索引字段最好不要为NULL,因为NULL让统计更加复杂并且需要额外的存储空间。

  • 复合索引有最左前缀的特性,如果复合索引能全部使用上,则是复合索引字段的索引长度之和,这也可以用来判定复合索引是否部分使用,还是全部使用。

执行计划-ref

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

EXPLAIN
select * from s1 ,s2 where s1.id = s2.id and s1.name = ‘enjoy’;

![image-20210103105123589](2020-12-30 02-执行计划.assets/image-20210103105123589.png)

由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即’ac’。

执行计划-rows

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

![image-20210103105814900](2020-12-30 02-执行计划.assets/image-20210103105814900.png)

执行计划-Extra

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

描述
Using filesort 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。 MySQL中无法利用索引完成的排序操作称为“文件排序”
Using temporary 使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
USING index 是否用了覆盖索引
Using where 表明使用了where过滤
Using join buffer 使用了连接缓存
Impossible where where子句的值总是false,不能用来获取任何元组
Using filesort

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

![image-20210103110030022](2020-12-30 02-执行计划.assets/image-20210103110030022.png)

![image-20210103110040531](2020-12-30 02-执行计划.assets/image-20210103110040531.png)

Using temporary

使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。

常见于排序order by和分组查询group by。

![image-20210103110141643](2020-12-30 02-执行计划.assets/image-20210103110141643.png)

![image-20210103110150210](2020-12-30 02-执行计划.assets/image-20210103110150210.png)

USING index

表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。

如果同时出现Using where,表明索引被用来执行索引键值的查找。

![image-20210103110245291](2020-12-30 02-执行计划.assets/image-20210103110245291.png)

如果没有同时出现using where,表明索引用来读取数据而非执行查找动作:

![image-20210103110320817](2020-12-30 02-执行计划.assets/image-20210103110320817.png)

覆盖索引(Covering Index),也叫做索引覆盖。

理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。

理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。

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

Using where

表明使用了where过滤。

Using join buffer

使用了连接缓存。

![image-20210103110645767](2020-12-30 02-执行计划.assets/image-20210103110645767.png)

Impossible where

where子句的值总是false,不能用来获取任何元组。

![image-20210103110716498](2020-12-30 02-执行计划.assets/image-20210103110716498.png)