项目开发中,性能是我们比较关注的问题,特别是数据库的性能;作为一个开发,经常和SQL语句打交道,想要写出合格的SQL语句,我们需要了解SQL语句在数据库中是如何扫描表、如何使用索引的;

MySQL提供explain/desc命令输出执行计划,我们通过执行计划优化SQL语句。

下面我们以MySQL5.7为例了解一下执行计划:

注:文中涉及到的表结构、sql语句只是为了理解explain/desc执行计划,有不合理之处勿喷

explain/desc 用法

只需要在我们的查询语句前加 explain/desc即可

准备数据表

1 --创建user表

2 create table user(3 id int,4 name varchar(20),5 role_id int,6 primary key(id)7 )engine=innodb default charset=utf8;8 --创建role表

9 create tablerole(10 id int,11 name varchar(20),12 primary key(id)13 )engine=innodb default charset=utf8;

查询,执行计划

1 explain select * from user;

执行计划输出有id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、extra,这些内容有什么意义,下面简单介绍一下

explain/desc 输出详解

一、id ,select 查询序列号

1 id相同,从上往下一次执行;

1 --左关联

2 explain select * from user a left join user b on a.id=b.id;3 --右关联

4 explain select * from user a right join user b on a.id=b.id;

通过left join 和 right join 验证;id一样(注意执行计划的table列),left join 先扫描a表,再扫描b表;right join 先扫描b表,再扫描a表

2 id不同,id越大优先级越高,越先被执行

1 desc select * from user where role_id=(select id from role where name='开发');

我们编写查询角色为开发的用户;可以知道先查询角色name为开发角色id,查询序列号为2;再根据角色id查询用户,查询序列号为1;

二、select_type,查询语句类型

(1) SIMPLE(简单SELECT,不使用UNION或子查询等)

1 explain select * from user;

(2) PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)

1 desc select * from user where role_id=(select id from role where name='开发');

(3) UNION(UNION中的第二个或后面的SELECT语句)

1 desc select * from user where name='Java' union select * from user where role_id=1;

(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)

1 desc select * from usera2 where id in(3 select b.id from user b where b.id=a.id union

4 select c.id from role c where c.id=a.role_id5 );

(5) UNION RESULT(UNION的结果)

1 desc select * from user where name='Java' union select * from user where role_id=1;

(6) SUBQUERY(子查询中的第一个SELECT)

1 desc select * from user where role_id=(select id from role where name='开发');

(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)

1 desc select * from user where role_id = ( select id from role where id=user.id );

(8) DERIVED(派生/衍生表的SELECT, FROM子句的子查询)

1 desc select * from ( select * from user where name='Java' union select * from user where role_id=1 ) a;

(9) MATERIALIZED(物化子查询) 在SQL执行过程中,第一次需要子查询结果时执行子查询并将子查询的结果保存为临时表 ,后续对子查询结果集的访问将直接通过临时表获得。

(10) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

(11) UNCACHEABLE UNION(UNION查询的结果不能被缓存)

三、table,查询涉及的表或衍生表

table分别user、role表

四、partitions查询涉及到的分区

创建分区表,

1 --创建分区表,

2 --按照id分区,id<100 p0分区,其他p1分区

3 create table user_partitions (id intauto_increment,4 name varchar(12),primary key(id))5 partition byrange(id)(6 partition p0 values less than(100),7 partition p1 valuesless than maxvalue8 );

1 desc select * from user_partitions where id>200;

查询id大于200(200>100,p1分区)的记录,查看执行计划,partitions是p1,符合我们的分区规则

五、type 提供了判断查询是否高效的重要依据依据

通过 type 字段, 我们判断此次查询是全表扫描还是索引扫描等,下面简单介绍一下常用的type;

(1)system: 表中只有一条数据,相当于系统表; 这个类型是特殊的 const 类型;

(2)const: 主键或者唯一索引的常量查询,表格最多只有1行记录符合查询,通常const使用到主键或者唯一索引进行定值查询。

主键

1 --创建user表

2 create table user(id int primary key, name varchar(20), role_id int);3 --插入一条记录

4 insert into user values (1, 'a', 1);5 --按id查询

6 desc select * from user where id=1;7 --按role_id查询

8 desc select * from user where role_id=1;

分别查看按id和按role_id查询的执行计划;发现按主键id查询,执行计划type为const

将主键设置为id和role_id

1 --删除主键

2 alter table user drop primary key;3 --设置主键id,role_id

4 alter table user add primary key(id,role_id);5 --按照部分主键查询

6 desc select * from user where id=1;7 --按照部分主键查询

8 desc select * from user where role_id=1;9 --按照全部主键查询

10 desc select * from user where id=1 and role_id=1;

发现只有按照全部主键查询,执行计划type为const

唯一索引

1 --删除主键

2 alter table user drop primary key;3 --设置主键

4 alter table user add primary key(id);5 --设置role_id为唯一索引

6 alter table user add unique keyuk_role(role_id);7 --按照唯一索引查询

8 desc select * from user where role_id=1;

发现按role_id唯一索引查询;执行计划type为const

普通索引

1 --将role_id设置成普通索引

2 --删除唯一索引

3 alter table user drop indexuk_role;4 --设置普通索引

5 alter table user add indexindex_role(role_id);6 --按照普通索引查询

7 desc select * from user where role_id=1;

发现按role_id普通索引查询;执行计划type为ref

const用于主键或唯一索引查询;将PRIMARY KEY或UNIQUE索引的所有部分与常量值进行比较时使用;与索引类型有关。

(3)eq_ref: 除了system和const类型之外,效率最高的连接类型;唯一索引扫描,对于每个索引键,表中只有一条记录与之对应;常用于主键或唯一索引扫描

准备数据

1 --创建teacher表

2 create table teacher( id int primary key, name varchar(20), tc_id int);3 --插入3条数据

4 insert into teacher values (1,'a',1),(2,'b',2),(3,'c',3);5 --创建teacher_card表

6 create table teacher_card( id int primary key, remark varchar(20) );7 --插入2条数据

8 insert into teacher_card values (1,'aa'),(2,'bb');9 --关联查询,执行计划

10 desc select * from teacher t join teacher_card tc on t.tc_id=tc.id where t.name='a';

执行计划

根据上面的知识;可知id相同,由上至下依次执行,分析结果可知:

先查询t表就是teacher表中name字段为a的记录,由于name字段没有索引,所以全表扫描(type:ALL),一共有3条记录,扫描了3行(rows:3),1条符合条件(filtered:33.33 1/3);

再查询tc即teacher_card表使用主键和之前的t.tc_id关联;由于是关联查询,并且是通过唯一索引(主键)进行查询,仅能返回1或0条记录,所以type为eq_ref。

1 --删除teacher_card主键

2 alter table teacher_card drop primary key;3 --这是teacher_card.id为唯一索引

4 alter table teacher_card add unique keyui_id(id);5 --关联查询,执行计划

6 desc select * from teacher t join teacher_card tc on t.tc_id=tc.id where t.name='a';

分析结果,将teacher_card的id设置为唯一索引,type为eq_ref;满足仅能返回1或0条记录。

1 --删除teacher_card唯一索引

2 alter table teacher_card drop indexui_id;3 --设置teacher_card.id为普通索引

4 alter table teacher_card add indexindex_id(id);5 --关联查询,执行计划

6 desc select * from teacher t join teacher_card tc on t.tc_id=tc.id where t.name='a';

分析结果,将teacher_card的id设置为普通索引,type为ref;不满足仅能返回1或0条记录。

equ_ref用于唯一索引查询,对每个索引键,表中只有一条或零条记录与之匹配。

(4)ref:此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了最左前缀规则索引的查询(换句话说,连接不能基于键值选择单行,可能是多行)。

1 --teacher.tc_id无索引,执行计划

2 desc select * from teacher t join teacher_card tc on t.tc_id=tc.id where tc.remark='aa';3 --设置teacher.tc_id为普通索引

4 alter table teacher add indexindex_tcid(tc_id);5 --teacher.tc_id有索引,执行计划

6 desc select * from teacher t join teacher_card tc on t.tc_id=tc.id where tc.remark='aa';

先查询tc表就是teacher_card表中remark字段为aa的记录,由于remark字段没有索引,所以全表扫描(type:ALL),一共有2条记录,扫描了2行(rows:2),1条符合条件(filtered:50,1/2);

tc_id无索引 再查询t即teacher表使用tc_id和之前的tc.id关联;由于是关联查询,不是索引,全表扫描,所以type为ALL。

tc_id有索引 再查询t即teacher表使用tc_id和之前的tc.id关联;由于是关联查询,索引扫描,能返回0或1或多条记录,所以type为ref。

(5)range: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, , BETWEEN, IN() 操作中。

1 desc select * from teacher where id>2;2 desc select * from teacher where id in (1,2,3);

(6)index: 扫描索引树

如果索引是复合索引,并且复合索引列满足select所需的所有数据,则仅扫描索引树。在这种情况下,Extra为Using index。仅索引扫描通常比ALL索引的大小通常小于表数据更快。

索引列不满足select所需的所有数据,此时需要回表扫描;按索引顺序查找数据行。Uses index没有出现在Extra列中。

1 --查看teacher表索引

2 show index fromteacher;3 --查询tc_id,执行计划

4 desc select tc_id fromteacher;5 --按tc_id索引分组,执行计划

6 desc select name from teacher group by tc_id;

查询tc_id,扫描索引树,type为index,Extra为 Using index;

按tc_id分组,全表扫描,以按索引顺序查找数据行。

(7)ALL: 全表扫描,没有任何索引可以使用时。这是最差的情况,应该避免。

1 --查看teacher表索引

2 show index fromteacher;3 desc select * from teacher where name='a';

由于name字段不存在索引,type:ALL全表扫描;可通过对name字段设置索引进行优化。

六、possible_keys:指示MySQL可以从中选择查找此表中的行的索引。

七、key:MySQL查询实际使用到的索引。

1 --创建course表

2 create table course(id int primary key,name varchar(20),t_id int,key index_name(name),keyindex_tid(t_id));3 --插入数据

4 insert into course values (1,'Java',1), (2,'Python',2);5 --查询1

6 desc select * from course where name='Java' or t_id=1;7 --查询2

8 desc select * from course where name='Java';

查看执行计划

查询1,查询name为Java或t_id为1的记录;可能用到的索引possible_keys为index_name,index_tid;实际用到的索引key为NULL

查询2,查询name为Java;可能用到的索引possible_keys为index_name;实际用到的索引key为index_name

八、key_len:表示索引中使用的字节数(只计算利用索引作为index key的索引长度,不包括用于group by/order by的索引长度)

一般地,key_len 等于索引列类型字节长度,例如int类型为4 bytes,bigint为8 bytes;

如果是字符串类型,还需要同时考虑字符集因素,例如utf8字符集1个字符占3个字节,gbk字符集1个字符占2个字节

若该列类型定义时允许NULL,其key_len还需要再加 1 bytes

若该列类型为变长类型,例如 VARCHAR(TEXT\BLOB不允许整列创建索引,如果创建部分索引也被视为动态列类型),其key_len还需要再加 2 bytes

字符集会影响索引长度、数据的存储空间,为列选择合适的字符集;变长字段需要额外的2个字节,固定长度字段不需要额外的字节。而null都需要1个字节的额外空间,所以以前有个说法:索引字段最好不要为NULL,因为NULL让统计更加复杂,并且需要额外一个字节的存储空间。

1 --key_len的长度计算公式:

2

3 --varchar(len)变长字段且允许NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)

4 --varchar(len)变长字段且不允许NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)+2(变长字段)

5

6 --char(len)固定字段且允许NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)

7 --char(len)固定字段且不允许NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)

下面验证一下结论:

1 --创建user_info表

2 create table user_info( id int comment '主键',3 name varchar(10) character set utf8 not null comment '姓名',4 alias_name varchar(10) character set utf8 comment '姓名',5 role_id int comment '角色id',6 remark varchar(10) character set gbk not null comment '备注',7 primary key(id),8 keyindex_name(name),9 keyindex_alias(alias_name),10 keyindex_role(role_id),11 keyindex_remark(remark)12 )engine=innodb;13 --插入数据

14 insert into user_info values (1,'a','aa',1,'aaa');15 --按主键查询

16 desc select * from user_info where id=1;17 --按索引role_id查询

18 desc select * from user_info where role_id=1;

按照主键id查询possible_keys为primary,实际用到的索引key为primary,key_len为4;

按照索引role_id查询possible_keys为index_role,实际用到的索引key为index_role,key_len为5;

分析结果:按照role_id比按照id(均为int类型)的key_len大5-4=1,因为role_id可以为null,需要一个标志位;

1 --按照name查询 varchar(10) not null utf8 一个字符占3个字节 10*3+2(变长)=32

2 desc select * from user_info where name='a';3 --按照alias_name查询 varchar(10) utf8 一个字符占3个字节 10*3+2(变长)+1(null标志位)=33

4 desc select * from user_info where alias_name='aa';

按照name查询possible_keys为index_name,实际用到的索引key为index_name,key_len为32=10*3+2(变长);

按照alias_name查询possible_keys为index_alias,实际用到的索引key为index_alias,key_len为33=10*3+2(变长)+1(null标志位);

分析结果:name与remark均为变长且字符集一致,remark可以为null,33-32=1多占一个标志位;

1 --按照name查询 varchar(10) not null utf8 一个字符占3个字节 10*3+2(变长)=32

2 desc select * from user_info where name='a';3 --按照remark查询 varchar(10) not null gbk 一个字符占2个字节 10*2+2(变长)=22

4 desc select * from user_info where remark='aaa';

按照name查询possible_keys为index_name,实际用到的索引key为index_name,key_len为32=10*3(utf8一个字符3个字节)+2(变长);

按照remark查询possible_keys为index_remark,实际用到的索引key为index_remark,key_len为22=10*2(gbk一个字符2个字节)+2(变长);

分析结果:name与remark均为变长但字符集不一致,分别为utf8与gbk;符合公式;

1 --将name修改为char(10) 定长 character set utf8 not null

2 alter table user_info modify name char(10) character set utf8 not null;3 --按照name查询 varchar(10) not null utf8 一个字符占3个字节 10*3=30

4 desc select * from user_info where name='a';

按照name查询possible_keys为index_name,实际用到的索引key为index_name,key_len为30;

因为将name修改为char(10) 定长 character set utf8 not null,10*3=30;符合公式

九、ref:显示该表的索引字段关联了哪张表的哪个字段

1 desc select * from user,role where user.role_id=role.id;

通过执行计划可知,role表执行计划ref为study.user.role_id;说明role.id关联user.role_id;

十、rows:根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,数值越小越好

十一、filtered:返回结果的行数占读取行数的百分比,值越大越好

--查看teacher数据

select * fromteacher;--查看teacher_card数据

select * fromteacher_card;--查询语句

select * from teacher t join teacher_card tc on t.tc_id=tc.id where t.name='a';--执行计划

desc select * from teacher t join teacher_card tc on t.tc_id=tc.id where t.name='a';

根据上面的知识;可知id相同,由上至下依次执行,分析结果可知:

先查询t表就是teacher表中name字段为a的记录,由于name字段没有索引,所以全表扫描(type:ALL),一共有3条记录,扫描了3行(rows:3),1条符合条件(filtered:33.33 1/3);

再查询tc即teacher_card表使用主键和之前的t.tc_id关联;扫描索引(type:ref),返回1条记录,最终返回1条记录,(filtered:100 1/1)。

十二、extra:包含不适合在其他列中显示但十分重要的额外信息。常见的值如下

use filesort:MySQL会对数据使用非索引列进行排序,而不是按照索引顺序进行读取;若出现改值,应优化索引

1 --查看user索引

2 show index from user;3 --查询name并排序

4 desc select name from user order byname;5 --为name列设置索引,优化

6 alter table user add indexindex_name(name);7 --查询name并排序

8 desc select name from user order by name;

use temporary:使用临时表保存中间结果,比如,MySQL在对查询结果排序时使用临时表,常见于order by和group by;若出现改值,应优化索引

use index:表示select操作使用了索引覆盖,避免回表访问数据行,效率不错

use where:where子句用于限制哪一行

1 --创建student表

2 create tablestudent(3 id int,4 first_name varchar(10),5 last_name varchar(10),6 primary key(id),7 keyindex_first(first_name)8 )engine=innodb default charset=utf8;9 --插入数据

10 insert into student values (1,'a','b');11 --按照first_name查找

12 desc select first_name,last_name from student where first_name='a';13 --设置first_name,last_name复合索引

14 alter table student drop indexindex_first;15 alter table student add indexindex_name(first_name,last_name);16 --按照first_name查找

17 desc select first_name,last_name from student where first_name='a';

分析结果:

当设置first_name为普通索引(单列索引),按照first_name查询;type:ref、possible_keys:indes_first、key:indes_first、extra:null,用到索引;

当设置first_name,last_name为复合索引(联合索引),按照first_name查询;type:ref、possible_keys:indes_name、key:indes_name、extra:Using index;type:ref用到索引,因为是复合索引不需要回表扫描,extra:Using index索引覆盖;注意此时key_len为33=10*3(utf8)+2(变长)+1(null标志位),用到了复合索引的一部分即first_name

当设置first_name,last_name为复合索引(联合索引),按照last_name查询;type:index、possible_keys:null、key:indes_name、extra:Using where,Using index;type:index而不是ref,扫描索引树,复合索引的最左原则;此时key_len为66=10*3(utf8)+2(变长)+1(null)+10*3(utf8)+2(变长)+1(null标志位);Using where应where子句进行限制

小结:

根据MySQL执行计划的输出,分析索引使用情况、扫描的行数可以预估查询效率;进而可以重构SQL语句、调整索引,提升查询效率。

本文只是简单介绍一下MySQL执行计划,想全面深入了解MySQL,可优先阅读MySQL官方手册。

mysql的执行计划_MySQL——执行计划相关推荐

  1. mysql 并发数 任务调度_mysql的计划任务与事件调度实例分析

    mysql的计划任务与事件调度实例分析 发布时间:2020-08-22 17:46:02 来源:脚本之家 阅读:98 作者:luyaran 本文实例讲述了mysql的计划任务与事件调度.分享给大家供大 ...

  2. mysql查询sql计划_MySQL 查询计划 总结

    (一)查询计划 释义 查询计划: SQL语句是怎样进行执行的 使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的.分析你的查询语句或 ...

  3. mysql使用常量列_MySQL 执行计划

    1.使用explain语句去查看分析结果 如explain select * from test1 where id=1; 会出现:id selecttype table type possible_ ...

  4. mysql 执行计划_mysql执行计划

    执行计划使用explain sql查询. 1. 构造数据 usecoshaho002;drop table if existsinfo;create tableinfo( idint primary ...

  5. 引导mysql执行计划_Mysql执行计划详解

    --歌手表 CREATE TABLE`singer` ( `id`int(11) unsigned NOT NULL, `update_time`datetime DEFAULT NULL, `nam ...

  6. mysql创建每天执行计划_mysql执行计划

    执行计划分析 什么是执行计划? select * from  city where sname='张飞'; 分析的是优化器按照内置的代价计算模型算法, 最终选择后的执行计划. cost ? 代价,成本 ...

  7. mysql学习计划_MySQL学习计划

    2019.1.15更新 没有按照既定计划执行,借口当然是一大堆,耐心得读完了这本书,受益匪浅,解决了我日报和周报中的很多问题,虽然离目标还有很大的距离,但是这本书让我看到了希望,科技改变世界,这一行行 ...

  8. mysql load报错_mysql:执行LOAD DATA LOCAL 报错

    mysql:执行LOAD DATA LOCAL 报错. 我使用navicat for mysql 连接 mysql服务器执行local 可以正常执行 . show VARIABLES like  '% ...

  9. mysql的从节点能否执行事务_MySQL执行事务的语法与流程详解

    摘要:MySQL 提供了多种存储引擎来支持事务. MySQL 提供了多种存储引擎来支持事务.支持事务的存储引擎有 InnoDB 和 BDB,其中,InnoDB 存储引擎事务主要通过 UNDO 日志和 ...

  10. mysql delete in死锁_mysql 执行delete引发死锁问题

    关于mysql事务引发的死锁异常解决 场景 问题 死锁日志[^2] 锁类型与隔离级别 InnerDB 锁: mysql事务 解决方案 场景 mysql 5.7 InnoDB存储引擎 jdk 8 spr ...

最新文章

  1. php web目录结构,目录结构
  2. CSS 特效 (教程还是英文的好)
  3. python学习路线-Python学习路线图(2020年最新版)
  4. 2020年行政区划代码_2020年梧州市行政区划,了解梧州市有几个区,详细数据
  5. java poi 读取excel 编码_Java使用POI 读取和写入Excel指南
  6. python文件处理系列(二):Excel文件读取库xlwings
  7. NeurIPS2019无人驾驶研究成果大总结(含大量论文及项目数据)
  8. 洛谷P2463 [SDOI2008]Sandy的卡片(后缀数组SA + 差分 + 二分答案)
  9. 7.2.Zeng_Cache(3) --- 前端
  10. 计算机学科技术前沿:INFOCOM和SIGCOMM会议信息
  11. 千峰商城-springboot项目搭建-06-数据库创建
  12. Unity线性空间UI的问题
  13. 域内计算机本地管理员密码管理
  14. Android 仿京东淘宝多规格选择
  15. local variable xxx referenced before assignment
  16. Android-SDK下载和安装
  17. python中nx_Siemens NX (UG)中对于Python第三方包(函数库)的设置方法
  18. Smartbi可视化自助仪表盘教学(三)——调整仪表盘布局发布至手机
  19. 华中科技大学计算机本校保研,2018 华中科技大学计算机学院保研机试真题
  20. 大数乘法运算(C语言)

热门文章

  1. ComputeShader
  2. Ubuntu扩展磁盘内存
  3. Ubuntu老是提示系统内存空间不足---扩展内存方法
  4. 虚拟服务器有没有加入bt端口,bt端口映射怎么做?
  5. 计算机图形系统根据硬件配置,计算机图形系统硬件基础.ppt
  6. 利用python爬取租房信息_Python实战:爬取租房信息
  7. vue 使用代理(proxyTable)报错500
  8. redis hset hget字典的实现
  9. Kotlin中let、also、with、run和apply使用
  10. 八大排序之堆排序、快速排序、基数排序(java)。