聊到mysql数据库的优化,大家基本都会谈论
explain
关键字,确认sql是否使用数据库表中建立的索引,然后讨论sql语句或者索引优化方案等等~,那本篇文章主要谈论一下
explain
的理解。主要分为
理论和
实践相结合。
理论Part
概念
我们先了解一下explain
语法和相关理论知识。
语法:
EXPLAIN SELECT select_options
;
select_options
是select语句的查询选项,包括from where子句
等等。- 执行该语句,可以分析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
-
simple
简单的select查询,查询中不包含子查询或者UNION;
-
primary和subquery
primary:查询中若包含任何复杂的子部分,标记最外层查询语句;
subquery:在select
或where
列表中包含子查询
,标记子查询语句;
explain select (select id from teacher) from class;
subquery子查询teacher表,外层select为primary。
这条sql语句可以依据ID列,区分SQL语句的执行顺序。
- derived
在from列表中包含的子查询被标记为derived
(衍生),MySQL会递归执行这些子查询,把结果放到临时表
中。
explain select * from (select * from teacher limit 1) tmp;
依据ID=2,先查询teacher表,然后执行最外层查询,并将结果存入临时表。
- 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
输出的行所引用的表;
- 当
from 子句中有子查询
时,table列是<derivenN>
格式,表示当前查询**依赖 id=N **的查询,于是先执行 id=N 的查询。
- 当有
union
时,UNION RESULT 的 table 列的值为<union1,2>
,1和2表示参与 union 的 select行id。
type
-
null
MySql优化器能够在优化阶段分解查询语句,在执行阶段就不用访问表或索引。
-
system
只有一条数据的系统表 或 衍生表<derived>
只有一条数据的主查询才会出现,可以忽略掉,没有太大意义。
-
const
表示通过索引一次就找到了,const用于比较primary key 或者 unique索引
(查询类型与索引类型有关)。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const。
where 语句中使用主键索引作为条件。 -
eq_ref
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或 唯一索引扫描。
primarykey 或 unique key
索引的所有部分被连接使用,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的连接类型了,简单的 select 查询不会出现这种 type。
id列都是1,当id列值一样时,从上到下执行表。所以先执行class_teacher表,后执行class表。 -
ref
相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀
,索引要和某个值相比较,可能会找到多个符合条件的行。
-
range
使用索引列检索指定范围,where后面是一个范围查询(between and,in ,>, <, >=)。
- index
查询全部索引中的数据即只有索引树被扫描
;因为索引文件通常比数据文件小,故通常比ALL快一些。
注意:class
表,上面创建表时,建立class_name索引;同样的查询用于teacher
表中,便会全表扫描。
- 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 + 2;
2)数值类型
tinyint:1字节
smallint:2字节
int:4字节
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
写到这里终于到最后一列啦,再坚持一下下~
-
Using index
Extra显示Using Index,说明用到了索引,是性能高的表现。一般出现在查询的列被索引列覆盖。
-
Using where
Extra显示Using where,表示没有用到索引,查询的列未被索引列覆盖。
-
Using where Using index
Extra显示Using whre Using index,表示查询的列被索引列覆盖,并且where筛选条件是索引列之一,但不是最左原则中第一个索引,常出现在联合索引场景。
-
NULL
Extra显示null,表示查询的列未被索引列覆盖,并且where筛选条件是索引的前导列,说明用到了索引,
但是部分字段未被索引列覆盖,必须通过“回表”来实现,所以不是纯粹地用到了索引,也不是完全没用到索引。
- Using index condition
Extra显示Using index condition与Using where类似,查询的列不完全被索引列覆盖,where条件中是一个前导列的范围。
总结
溪源花费三个晚上终于整理完了这篇文章,希望能够帮助到大家,得到大家的支持,若存在不正之处,望大佬们积极指正。
大家记得
一键三连呀~
转载:https://blog.csdn.net/xuan_lu/article/details/109210620