Explain SQL的执行计划

explain select * from books;

5.7之前版本显示partitions、filtered

explain extended select * from books;
explain partitions select * from books;

explain中的列信息

1. id列

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

2. select_type列

select_type表示对应行是简单还是复杂的查询。
1)simple:简单查询。查询不包含子查询和union
2)primary:复杂查询中最外层的select
3)subquery:包含在select中的子查询(不在from子句中)
4)derived:包含在from子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义)
5)union:在union中的第二个和随后的select

用这个例子来了解primary、subquery和derived类型

set session optimizer_switch='derived_merge=off'; #关闭mysql5.7新特性对衍生表的合并优化
explain select ( select 1 from books where id=1) from ( select * from brands where id=1) bb;

3. table列

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

4. type列

这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
依次从最优到最差分别为:system>const>eq_ref>ref>range>index>ALL

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

SQL执行流程 《SQl解析->查询优化->SQL路由->SQL改写->SQL执行->结果归并》

  • NULL: mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。

    例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表

    explain select min(id) from books;
    

  • const,system:mysql能对查询的某部分进行优化并将其转化成一个常量(可以看showwarnings的结果)。用于primarykey或uniquekey的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是const的特例,表里只有一条元组匹配时为system
explain extended select * from (select * from books where id=1) tmp;

show warnings;

  • eq_ref:primary key或unique key索引的所有部分被连接使用,最多只会返回一条符合条件的记录。这可能是在const之外最好的联接类型了,简单的select查询不会出现这种type。
explain select * from brand_book left join brands on brand_book.brand_id =brands.id;

  • ref:相比eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
  1. 简单select查询,category是普通索引(非唯一索引)
explain select * from books where category = 'online'

  • range:范围扫描通常出现在in(),between,>,<,>=等操作中。使用一个索引来检索给定范围的行。
explain select * from books where id > 1;

  • index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些。
create table `category`(`id` int(11) not null,`name` varchar(255) default null,primary key (`id`),key `index_name`(`name`) using btree
) ENGINE=INNODB DEFAULT CHARSET=utf8;explain select * from category;

explain select book_id from books left join brand_book on books.id = brand_book.book_id

  • ALL:即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。
explain select * from books;

5.possible_keys列

这一列显示查询可能使用哪些索引来查找。

explain时可能出现possible_keys有列,而key显示NULL的情况,这种情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。

如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查where子句看是否可以创造一个适当的索引来提高查询性能,然后用explain查看效果。

6.key列

这一列显示mysql实际采用哪个索引来优化对该表的访问。

如果没有使用索引,则该列是NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用forceindex、ignoreindex。

7.key_len列

这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。

举例来说,brand_book的联合索引idx_brand_book_id由brand_id和booke_id两个int列组成,并且每个int是4字节。通过结果中的key_len=4可推断出查询使用了第一个列:book_id列来执行索引查找。

explain select * from brand_book where brand_id = 1; ### key_len=4
explain select * from brand_book where brand_id = 1 and book_id = 1; ### key_len=8

key_len计算规则如下:

  • 字符串,char(n)和varchar(n),5.0.3以后版本中,n均代表字符数,而不是字节数,如果是utf-8,一个数字或字母占1个字节,一个汉字占3个字节

    • char(n):如果存汉字长度就是3n字节
    • varchar(n):如果存汉字则长度是3n+2字节,加的2字节用来存储字符串长度,因为varchar是变长字符串
  • 数值类型

    • tinyint:1字节
    • smallint:2字节
    • int:4字节
    • bigint:8字节
  • 时间类型

    • date:3字节
    • timestamp:4字节
    • datetime:8字节
  • 如果字段允许为NULL,需要1字节记录是否为NULL

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

8. ref列

这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:book.id)

9. rows列

这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。

10. Extra列

这一列展示的是额外信息。常见的重要值如下:

1)Using index:使用覆盖索引

覆盖索引定义:mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值

explain select book_id from brand_book where brand_id = 1;

2)Using where:使用where语句来处理结果,并且查询的列未被索引覆盖

explain select * from book where name='a'

3)Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围

explain select * from brand_book where brand_id > 1

4)Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化

explain select distinct name from brands;

5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的

explain select * from books order by name;

6)Select tables optimized away:使用某些聚合函数(比如max、min)来访问存在索引的某个字段是

explain select min(id) from books;

索引实践

1.全值匹配

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' ;

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 22;

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 22 AND position = 'manager';

2.最左前缀法则

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。

EXPLAIN SELECT * FROM employees WHERE name = 'Bill' and age = 31;

EXPLAIN SELECT * FROM employees WHERE age = 30 AND position = 'dev';

EXPLAIN SELECT * FROM employees WHERE position = 'manager';

3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';

EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';

给hire_time增加一个普通索引:

ALTER TABLE `employees` ADD INDEX `idx_hire_time`(`hire_time`) USING BTREE;
EXPLAIN select * from employees where date(hire_time) = '2018‐09‐30';

转化为日期范围查询,有可能会走索引:

EXPLAIN select * from employees where hire_time >= '2018‐09‐3000:00:00' and hire_time <= '2018‐09‐3023:59:59';

还原最初索引状态

ALTER TABLE `employees` DROP INDEX `idx_hire_time`;

4.存储引擎不能使用索引中范围条件右边的列

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age=22 AND position = 'manager';

重点理解 name 有序 age > 22 有序 position 无序(不能走索引)

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age>22 AND position = 'manager';

5.尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select*语句

EXPLAIN SELECT name, age FROM employees WHERE name = 'LiLei'AND age = 23 AND position = 'manager';

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 23 AND position = 'manager';

6.mysql在使用不等于(!=或者<>),notin,notexists的时候无法使用索引会导致全表扫描<小于、>大于、<=、>=这些,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引

EXPLAIN SELECT * FROM employees WHERE name != 'LiLei';

7.is null,is not null一般情况下也无法使用索引

EXPLAIN SELECT * FROM employees WHERE name is null

8.like以通配符开头(’$abc…’)mysql索引失效会变成全表扫描操作

EXPLAIN SELECT * FROM employees WHERE name like '%Lei'

like str%相当于=常量,%str和%str%相当于范围

EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'

问题:解决like’%字符串%'索引不被使用的方法?

a)使用覆盖索引,查询字段必须是建立覆盖索引字段

EXPLAIN SELECT name, age, position FROM employees WHERE name like '%Lei%';

b)如果不能使用覆盖索引则可能需要借助搜索引擎

9.字符串不加单引号索引失效

EXPLAIN SELECT * FROM employees WHERE name = '1000';

EXPLAIN SELECT * FROM employees WHERE name = 1000;

10.少用or或in,用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引,详见范围查询优化

在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';

11.范围查询优化

给年龄添加单值索引

ALTER TABLE `employees` ADD INDEX `idx_age`(`age`) USING BTREE;
explain select * from employees where age >=1 and age <= 2000;

没走索引原因:mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。
比如这个例子,可能是由于单次数据量查询过大导致优化器最终选择不走索引

优化方法:可以将大的范围拆分成多个小范围

explain select * from employees where age >= 1 and age<=1000;

explain select * from employees where age >= 1001 and age<=2000;

还原最初索引状态

ALTER TABLE `employees` DROP INDEX `idx_age`

like str%相当于=常量,%str和%str%相当于范围

索引下推(Index Condition Pushdown,ICP)

对于辅助的联合索引(name,age,position),正常情况按照最左前缀原则,
SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager'

这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和position是无序的,无法很好的利用索引。

在MySQL5.6之前的版本,这个查询只能在联合索引里匹配到名字是 ‘LiLei’ 开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再比对age和position这两个字段的值是否符合。

MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数。使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 ‘LiLei’ 开头的索引之后,同时还会在索引里过滤age和position这两个字段,拿着过滤完剩下的索引对应的主键id再回表查整行数据。

索引下推会减少回表次数,对于innodb引擎的表索引下推只能用于二级索引,innodb的主键索引(聚簇索引)树叶子节点上保存的是全行数据,所以这个时候索引下推并不会起到减少查询全行数据的效果。

为什么范围查找Mysql没有用索引下推优化?

估计应该是Mysql认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。

Mysql如何选择合适的索引

EXPLAIN select * from employees where name > 'a'; --没走联合索引

如果用name索引需要遍历name字段联合索引树,然后还需要根据遍历出来的主键值去主键索引树里再去查出最终数据,成本比全表扫描还高,可以用覆盖索引优化,这样只需要遍历name字段的联合索引树就能拿到所有结果,如下:

EXPLAIN select name,age,position from employees where name > 'a' ;
EXPLAIN select * from employees where name > 'zzz' ; --走联合索引

对于上面这两种 name>‘a’ 和 name>‘zzz’ 的执行结果,mysql最终是否选择走索引或者一张表涉及多个索引,mysql最终如何选择索引,我们可以用trace工具来一查究竟,开启trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后立即关闭
trace工具用法:

mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; --开启trace

mysql> select * from employees where name > 'a' order by position;
mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;

结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描

mysql> select * from employees where name > 'zzz' order by position;
mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;

查看trace字段可知索引扫描的成本低于全表扫描,所以mysql最终选择索引扫描

trace

steps: [{join_preparation:  {  --第一阶段:SQL准备阶段,格式化sql....},join_optimization:  {  --第二阶段:SQL优化阶段....},join_execution: {    --第三阶段:SQL执行阶段.......}}
]

mysql> set session optimizer_trace="enabled=off"; --关闭trace

详情:
https://gist.github.com/menxu/9997d3a3a28a806a940fa00648c2aa9b

强制走索引 force index(index_name)

SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';

虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高

table

create table `brands`(`id` int(11) not null,`name` varchar(255) default null,`remark` varchar(255) default null,primary key (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;create table `books`(`id` int(11) not null,`name` varchar(255) default null,`remark` varchar(255) default null,primary key (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;create UNIQUE index unique_index_book_name on books(name);create index unique_index_book_category on books(category);create table `brand_book`(`id` int(11) not null,`brand_id` int(11) not null,`book_id` int(11) not null,`remark` varchar(255) default null,primary key (`id`),key `idx_brand_book_id`(`brand_id`, `book_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;CREATE TABLE `employees` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',PRIMARY KEY (`id`),KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='员工记录表';INSERT INTO employees(name, age, position, hire_time) VALUES ('LiLei',22,'manager',NOW());
INSERT INTO employees(name, age, position, hire_time) VALUES ('HanMeimei',23,'dev',NOW());
INSERT INTO employees(name, age, position, hire_time) VALUES ('Lucy',23,'dev',NOW());

存储过程批量插入数据

drop PROCEDURE if EXISTS `employees_batch_insert`;
create PROCEDURE `employees_batch_insert`()
beginDECLARE i int;set i=1;while i<=10000 doINSERT INTO employees(name,age,position,hire_time) VALUES(CONCAT("MenDD",i),i,'dev',NOW());set i=i+1;end while;
end;
call `employees_batch_insert`();

MySQL(二)Explain相关推荐

  1. MySql数据库explain用法示例_mysql explain的用法

    MySQL的EXPLAIN命令显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 一.通过expalin可以得到 1.表的读取顺序 2.表的读 ...

  2. MySQL二十四:索引

    MySQL二十四:索引的介绍与分类 MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构. 提取句子主干,就可以得到索引的本质:索引是数据结构. 在一个表中,主键索引 ...

  3. Mysql之explain详解(超级全面)

    Mysql之explain详解(超级全面) 概念 explain能干嘛? 如何使用 输出字段解释 id(表的读取顺序) id相同,执行顺序由上至下 id不同,如果是子查询,id的序号会递增,id值越大 ...

  4. 【MySQL】explain 用法详解

    [MySQL]explain 用法详解   explain命令主要来查看SQL语句的执行计划,查看该SQL语句有没有使用索引,有没有做全表扫描等.它可以模拟优化器执行SQL查询语句,从而知道MySQL ...

  5. 学mysql是学指令吗_学习Mysql (二) 常用指令

    环境搭建OK.可视化查看看得我一脸懵逼,还是命令行学习下. 常用指令: 1. 登录: mysql -h 主机名 -u 用户名 -p 注:本机localhost的话,-h部分可以省略.-p后面不写密码的 ...

  6. JAVA中Explain注解用法,mysql之explain详解(分析索引最佳使用)

    mysql之explain详解(分析索引最佳使用) mysql explain用于分析sql 语句的执行及数据库索引的使用.本文将致力于帮助大家充分理解explain所返回的各项参数,从而使大家快速掌 ...

  7. mysql的explain怎么看_mysql中explain用法详解

    如果在select语句前放上关键词explain,mysql将解释它如何处理select,提供有关表如何联接和联接的次序. explain的每个输出行提供一个表的相关信息,并且每个行包括下面的列: 1 ...

  8. mysql查询优化explain命令详解

    转载自 mysql查询优化explain命令详解 mysql查询优化的方法有很多种,explain是工作当中用的比较多的一种检查方式.explain翻译即解释,就是看mysql语句的查询解释计划,从解 ...

  9. MySQL二十八规范数据库设计

    MySQL二十八:规范数据库设计 糟糕的数据库设计: ●数据冗余,浪费空间 ●数据库插入和删除都会麻烦.异常[ 屏蔽使用物理外键] ●程序的性能差 良好的数据库设计: ●节省内存空间 ●保证数据库的完 ...

  10. Mysql 的 Explain性能分析

    Mysql 的 Explain性能分析 概念 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的.分析你的查询语句或是表结构的性能瓶颈. 用法: E ...

最新文章

  1. proc/[pid]/maps 文件解释
  2. R语言distVincentySphere函数计算大圆距离实战(Great Circle Distance)
  3. IIS与ASP.NET对请求的处理
  4. java ui设计用什么_UI设计是什么?UI怎么设计?
  5. python前端学习-------Flask框架基础(建议收藏)
  6. React应用渲染界面的入口
  7. 一个透明的shader
  8. Principle --03
  9. python进度条 pyqt_pyqt中的进度条没有正确更新以读取fi
  10. 2022年考研数据结构_4 串
  11. 【转】Windows的多线程编程,C/C++
  12. vue-axios配置token,上传图片
  13. EDM营销模式分析和讲解
  14. 【POJ 2104】【主席树模板题】K-th Number
  15. 服务器不能用pe安装win7系统安装,WinPE无法安装win7系统的完美解决方案
  16. JMeter接口压力测试实战教程
  17. SpringCloud 配置管理:Nacos
  18. php session fixation,Session Fixation 攻防实战
  19. html css笔记zht
  20. win10在设备管理器里找不到蓝牙的三种解决办法

热门文章

  1. linux NFS 服务配置
  2. 1038 非诚勿扰第二期
  3. vue-cli3 配合 webpak DefinePlugin 构建期间自动检测环境变化,根据NODE_ENV引入配置文件
  4. WIN7开启AHCI蓝屏的解决方法
  5. 从零开始做App 系列之项目立项+预估时间篇
  6. 数据分析中常用到EXCEL快捷键合集
  7. 通过银行卡号解析银行名称和卡别
  8. 数据库系统概论笔记(第一章 引言)—— 持续更新,争取每周更新一章
  9. 【面试题】JS面试大全
  10. 数字经济的新底座,“IPv6+”揭开神秘面纱