小言_互联网的博客

【mysql系列】细谈explain执行计划之“谜”

547人阅读  评论(0)


聊到mysql数据库的优化,大家基本都会谈论 explain关键字,确认sql是否使用数据库表中建立的索引,然后讨论sql语句或者索引优化方案等等~,那本篇文章主要谈论一下 explain的理解。主要分为 理论实践相结合。

理论Part

概念

我们先了解一下explain语法和相关理论知识。
语法
EXPLAIN SELECT select_options;

  1. select_options是select语句的查询选项,包括from where子句等等。
  2. 执行该语句,可以分析EXPLAIN后面的select语句的执行情况,并且能够分析出所查询的表的一些特征。
    例如:EXPLAIN SELECT * FROM class;
    执行结果如图:

执行计划中各个列代表具体含义解释如下:

  • id:
查询的序号,包含一组数字,表示查询中执行select子句或操作表的顺序
1.id相同,执行顺序从上往下
2.id不同,id值越大,优先级越高,越先执行
  • select_type:
查询类型,主要用于区别普通查询,联合查询,子查询等的复杂查询
1.simple ——简单的select查询,查询中不包含子查询或者UNION
2.primary ——查询中若包含任何复杂的子部分,最外层查询被标记
3.subquery——在select或where列表中包含了子查询
4.derived——在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放到临时表中
5.union——如果第二个select出现在UNION之后,则被标记为UNION,如果union包含在from子句的子查询中,外层select被标记为derived,故在union中第二个及之后的select。
6.union result:UNION 临时表检索结果的select。
  • table:
输出的行所引用的表
  • partitions:
如果查询基于分区表,将会显示访问的是哪个区。
  • type:
显示连接类型,显示查询使用了何种类型,按照从最佳到最坏类型排序
1.system:表中仅有一行(=系统表)这是const联结类型的一个特例。
2.const:表示通过索引一次就找到,const用于比较primary key或者unique索引。因为只匹配一行数据,所以如果将主键置于where列表中,mysql能将该查询转换为一个常量
3.eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于唯一索引或者主键扫描,常用于连接查询。简单查询不会出现该类型
4.ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,是使用普通索引或者唯一性索引的部分前缀,它返回所有匹配某个单独值的行,可能会找多个符合条件的行,属于查找和扫描的混合体
5.range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是where语句中出现了between,in等范围的查询。这种范围扫描索引扫描比全表扫描要好,因为它开始于索引的某一个点,而结束另一个点,不用全表扫描
6.index:index 与all区别为index类型只遍历索引树。通常比all快,因为索引文件比数据文件小很多。
7.all:遍历全表以找到匹配的行
type常见类型从最优到最差:system > const > eq_ref > ref > range > index > ALL
注意:一般保证查询至少达到range级别,最好能达到ref。
  • possible_keys:
指出MySQL能使用哪个索引在该表中找到行
  • key:
显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。查询中如果使用覆盖索引,则该索引和查询的select字段重叠。
1.要想强制mysql使用或者忽视possible_key列中的索引,在查询中使用force index、use index或者ignore index。
  • key_len:
表示索引中使用的字节数,该列计算查询中使用的索引的长度在不损失精度的情况下,长度越短越好。如果键是NULL,则长度为NULL。该字段显示为索引字段的最大可能长度,并非实际使用长度。
  • ref:
显示索引的哪一列被使用了,如果有可能是一个常数,哪些列或常量被用于查询索引列上的值
  • rows:
根据表统计信息以及索引选用情况,大致估算出找到所需的记录所需要读取的行数
  • filtered:
指返回结果的行占需要读到的行(rows列的值)的百分比。
  • Extra:
包含不适合在其他列中显示,但是十分重要的额外信息
1、Using filesort:说明mysql会对数据适用一个外部的索引排序。而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作称为“文件排序”
2、Using temporary:使用了临时表保存中间结果,mysql在查询结果排序时使用临时表。常见于排序order by和分组查询group by。
3、Using index:表示相应的select操作用使用覆盖索引,避免访问了表的数据行。如果同时出现using where,表名索引被用来执行索引键值的查找;如果没有同时出现using where,表名索引用来读取数据而非执行查询动作。
4、Using where :表明使用where过滤
5、using join buffer:使用了连接缓存
6、impossible where:where子句的值总是false,不能用来获取任何元组
7、select tables optimized away:在没有group by子句的情况下,基于索引优化Min、max操作或者对于MyISAM存储引擎优化count(*),不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
8、distinct:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。

实践Part

use explain_detail;

DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `id` int(11) NOT NULL comment '教师id',
  `teacher_name` varchar(45) DEFAULT NULL comment '姓名',
  `teacher_no` varchar(45) DEFAULT NULL comment '教师编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
INSERT INTO `teacher` (`id`, `teacher_name`, `teacher_no`)
VALUES (1,'溪源a','150921'), (2,'溪源b','201010'), (3,'溪源c','200325');
 
DROP TABLE IF EXISTS `class`;
CREATE TABLE `class` (
  `id` int(11) NOT NULL AUTO_INCREMENT comment '班级ID',
  `class_name` varchar(10) DEFAULT NULL comment '班级名称',
  PRIMARY KEY (`id`),
  KEY `idx_class_name` (`class_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
INSERT INTO `class` (`id`, `class_name`)
VALUES (1,'java1'),(2,'java2'),(3,'java3');
 
# 班级教师关系表
DROP TABLE IF EXISTS `class_teacher`;
CREATE TABLE `class_teacher` (
  `id` int(11) NOT NULL,
  `class_id` int(11) NOT NULL comment '班级ID',
  `teacher_id` int(11) NOT NULL comment '教师ID'
  PRIMARY KEY (`id`),
  KEY `idx_class_teacher_id` (`class_id`,`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 # 插入数据
INSERT INTO `class_teacher` (`id`, `class_id`, `teacher_id`)
VALUES (1, 1, 1), (2, 1, 2), (3, 2, 1);

id

explain select (select id from teacher limit 1) from class;

理论知识中介绍到id值越大执行优先级越高,id值相同则从上往下执行,id为null最后执行。从图中ID列,我们看到ID=2的先执行即先查询teacher表。

select_type

  1. simple
    简单的select查询,查询中不包含子查询或者UNION;

  2. primary和subquery
    primary:查询中若包含任何复杂的子部分,标记最外层查询语句;
    subquery:在selectwhere列表中包含子查询,标记子查询语句;

explain select (select id from teacher) from class;

subquery子查询teacher表,外层select为primary。


这条sql语句可以依据ID列,区分SQL语句的执行顺序。

  1. derived
    from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放到临时表中。
explain select * from (select * from teacher limit 1) tmp;


依据ID=2,先查询teacher表,然后执行最外层查询,并将结果存入临时表。

  1. union、union result
    union:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived;故在union中第二个及之后的select。
    union result:从union临时表检索结果的select。
explain select * from teacher where id = 1 union select * from teacher;


id=1为primary;说明是做外层查询,即此条sql语句from前面的语句;
id=2为union;说明是union后面的查询语句;
id=null,标记为UNION RESULT,生成的临时表;
两个的结果合并为union result,供select 检索。
再次说明下id列:1,2,null,执行顺序2 --> 1 --> null。
先执行select 2,然后执行select 1,最后执行执行从两个笛卡尔积检索数据。

table

输出的行所引用的表;

  1. from 子句中有子查询时,table列是 <derivenN> 格式,表示当前查询**依赖 id=N **的查询,于是先执行 id=N 的查询。
  2. 当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select行id。

type

  1. null
    MySql优化器能够在优化阶段分解查询语句,在执行阶段就不用访问表或索引。

  2. system

只有一条数据的系统表衍生表<derived>只有一条数据的主查询才会出现,可以忽略掉,没有太大意义。

  1. const
    表示通过索引一次就找到了,const用于比较primary key 或者 unique索引(查询类型与索引类型有关)。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const。

    where 语句中使用主键索引作为条件。

  2. eq_ref
    唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或 唯一索引扫描。
    primarykey 或 unique key 索引的所有部分被连接使用,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的连接类型了,简单的 select 查询不会出现这种 type

    id列都是1,当id列值一样时,从上到下执行表。所以先执行class_teacher表,后执行class表。

  3. ref
    相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。

  4. range
    使用索引列检索指定范围,where后面是一个范围查询(between and,in ,>, <, >=)。

  1. index
    查询全部索引中的数据即只有索引树被扫描;因为索引文件通常比数据文件小,故通常比ALL快一些。

注意:class表,上面创建表时,建立class_name索引;同样的查询用于teacher表中,便会全表扫描。

  1. all
    MySQL将遍历全表以找到匹配的行。没有建立索引或索引失效,查询全表数据,开发中应尽量避免。

possible_keys

指出MySQL能使用哪些索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示 null)

key

key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中
如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

key_len

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

下面分别使用普通索引联合索引看下key_len具体数值;

使用主键索引,使用字节数4;

使用联合索引,key_len=8;

下面扩展一下key_len的计算规则:

1)字符串
char(n):n字节长度;
varchar(n)2字节存储字符串长度,如果是utf-8,则长度 3n + 22)数值类型
tinyint:1字节
smallint:2字节
int4字节
bigint:8字节  
3)时间类型
date:3字节
timestamp:4字节
datetime:8字节
如果字段允许为 NULL,需要1字节记录是否为 NULL。(这是为什么会比正常计算多1的原因)。
索引最大长度是768字节,当字符串过长时,MySql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。


ref

显示在key列索引中,表查找值所用到的列或常量,一般比较常见为const或字段名称。

rows

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

filtered

指返回结果的行占需要读到的行(rows列的值)的百分比。


从图中可以看到rows=3;指定数据溪源a记录数1条,故filtered = 1 / 3 * 100/100 = 33.33%,保留两位小数。


那这里为什么是1呢,因为覆盖索引列,不需要与全表对比;

Extra

写到这里终于到最后一列啦,再坚持一下下~

  1. Using index
    Extra显示Using Index,说明用到了索引,是性能高的表现。一般出现在查询的列被索引列覆盖。

  2. Using where
    Extra显示Using where,表示没有用到索引,查询的列未被索引列覆盖。

  3. Using where Using index
    Extra显示Using whre Using index,表示查询的列被索引列覆盖,并且where筛选条件是索引列之一,但不是最左原则中第一个索引,常出现在联合索引场景。

  4. NULL
    Extra显示null,表示查询的列未被索引列覆盖,并且where筛选条件是索引的前导列,说明用到了索引,
    但是部分字段未被索引列覆盖,必须通过“回表”来实现,所以不是纯粹地用到了索引,也不是完全没用到索引。

  1. Using index condition
    Extra显示Using index condition与Using where类似,查询的列不完全被索引列覆盖,where条件中是一个前导列的范围。

总结

溪源花费三个晚上终于整理完了这篇文章,希望能够帮助到大家,得到大家的支持,若存在不正之处,望大佬们积极指正。

大家记得
一键三连呀~


转载:https://blog.csdn.net/xuan_lu/article/details/109210620
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场