MySql使用explain关键字可以模拟优化器执行sql语句,我们就能够知道MySql会如何处理咱们的sql,
可以根据explain的分析结果和MySql底层数据结构优化sql。文章内容基于MySql 5.7.24分析,
不同MySql版本可能有差别。如果用的是MySql 5.6.X,差别应该不会很大。
MySql索引底层数据结构和算法:https://blog.csdn.net/yhl_jxy/article/details/88392411
数据脚本准备,有一个teacher(教师表),class(班级表),class_teacher(班级与对应上课老师关系表)。
这里不涉及到实际业务,只是通过实例分析explain相关内容。
# 教师
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`id` int(11) NOT NULL comment '教师编号',
`teacher_name` varchar(45) DEFAULT NULL comment '教师姓名',
`entry_time` date DEFAULT NULL comment '入职时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `teacher` (`id`, `teacher_name`, `entry_time`)
VALUES (1,'a','2010-09-22'), (2,'b','2011-10-26'), (3,'c','2013-12-25');
# 班级
DROP TABLE IF EXISTS `class`;
CREATE TABLE `class` (
`id` int(11) NOT NULL AUTO_INCREMENT comment '班级编号',
`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,'class1'),(2,'class2'),(3,'class3');
# 班级教师关系表
DROP TABLE IF EXISTS `class_teacher`;
CREATE TABLE `class_teacher` (
`id` int(11) NOT NULL comment '主键',
`class_id` int(11) NOT NULL comment '班级编号',
`teacher_id` int(11) NOT NULL comment '教师编号',
`remark` varchar(255) DEFAULT NULL comment '备注',
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`, `remark`)
VALUES (1, 1, 1, '一班的1号教师'), (2, 1, 2, '一班的2号教师'), (3, 2, 1, '二班的1号教师');
先用explain关键字执行下,看看能看到啥?
mysql> explain select * from teacher;
下面详细分析每一列的含义。
id列的编号一般情况下是select的序列号,一般有几个select就有几个id,并且id的顺序按select出现的顺序增长。
id值越大执行优先级越高,id值相同则从上往下执行,id为null最后执行。
mysql> explain select (select id from teacher limit 1) from class;
MySql先执行id为2的表,即先执行子查询从teacher表获取一条数据,然后再执行id为1的表class。
select_type表示查询类型,分为以下几类查询。
1)simple
简单查询(不包含子查询,不包含关联查询,比如join,union)。
mysql> explain select * from teacher;
2)primary
包含子查询最外层的select。
3)subquery
包含子查询中的子查询。
mysql> explain select (select 1 from teacher where id = 1) from class;
subquery子查询teacher表,外层select为primary。
4)derived
包含在from子句中的查询。MySql会将结果放在一张临时表中以供查询使用。
mysql> explain select * from (select 1) tmp;
在union中第二个及之后的select。
6)union result
从union临时表检索结果的select。
mysql> explain select 1 union select 2;
select 1为primary。select 2为union。
两个的结果合并为union result,供select 检索。
这里也可以顺便看下id列,1,2,null,执行顺序2 --> 1 --> null。
先执行select 2,然后执行select 1,最后执行执行从两个笛卡尔积检索数据。
table为访问的表名。根据上面id或select_type实例观察,总结表名的规则。
1)from子查询时,table为子表名或
mysql> explain select * from (select 1) tmp;
在table列可以看到
2)当有 union 时,UNION RESULT 的 table 列的值为
mysql> explain select 1 union select 2;
如果查询基于分区表,将会显示访问的是哪个区。
type列表示关联类型或访问类型,MySql决定如何查找表中的行,以及查询表数据行记录的大概范围。
type常见类型从最优到最差:system > const > eq_ref > ref > range > index > ALL,
如果你的sql通过explain分析,看到type是All,一般是需要优化了。
一般得保证查询时能达到range级别,如果能做到ref级别,就更好了。
1)null
MySql优化器能够在优化阶段分解查询语句,在执行阶段就不用访问表或索引。
mysql> explain select max(id) from teacher;
2)system,const
system(忽略):只有一条数据的系统表 或 衍生表只有一条数据的主查询才会出现,可以忽略掉,没有太大意义。
cons(偶尔能达到):只能查询到一条数据的SQL,用于primary key 或 unique索引(查询类型与索引类型有关)。
mysql> explain select * from (select * from class where id = 1) tmp;
3)eq_ref
primary key 或 unique key 索引被连接(join)使用 ,最多只会返回一条符合条件的记录。
对于每个索引键的关联查询,返回匹配唯一行数据(有且只有1个,不能多,不能少)。
简单的select 查询不会出现这种type。
mysql> explain select * from class_teacher left join class on class_teacher.class_id = class.id;
这里也可以看到id列都是1,当id列值一样时,从上到下执行表。所以先执行class_teacher表,后执行class表。
4)ref
相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,
可能会找到多个符合条件的行。
mysql> explain select * from class where class_name = 'class1';
5)range
range使用索引列检索指定范围,where后面是一个范围查询(between and,in ,>, <, >=)。
mysql> explain select * from teacher where id between 1 and 5;
6)index
查询全部索引中的数据,这通常比ALL快一些。
mysql> explain select * from class;
7)ALL
查询全表数据。没有索引,或索引失效,查询全部数据。
mysql> explain select * from teacher;
possible_keys显示可能用到的索引,该列值可能为空,也可能不为空。
1)不为null时
当不为null时,会显示可能用到的索引。
2)为null时
当该列值为null时,表示不会用到索引。当出现这种情况时,看下where条件是否用到了索引字段,
需要分析为什么没用索引或是否需要添加索引。
key列值显示MySql实际使用到的索引。如果没有使用索引,该列值为null。
MySql索引使用的字节数,通过该值计算出使用了索引中的哪些列。
比如class_teacher表中,有联合索引idx_class_teacher_id,由class_id和teacher_id字段组成。
使用一个索引字段或使用两个索引字段来观察ken_len的值。
mysql> explain select * from class_teacher where class_id = 1;
mysql> explain select * from class_teacher where class_id = 1 and teacher_id = 1;
从上面sql可以看到,key_len值分别为4和8,这个值是int=4个字节算出来的,
因为我们的class_id和teacher_id都是int类型,所以key_len存的是使用到的索引字段类型字节长度。
第一个查询sql只用class_id索引字段,所以key_len为4,第二个查询sql用到了class_id和teacher_id,
所以key_len计算为8,但是如果字段允许为null,则需要加1。key_len的计算规则如下:
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会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,一般比较常见为const或字段名称。
mysql> explain select * from class_teacher where class_id = 1 and teacher_id = 1;
key列联合索引class_id和teacher_id,使用到两个索引字段,所以为const,const。
MySql估计sql要查询出来的数据条数。
mysql> explain select * from class_teacher where class_id = 1;
MySql预估可能会查询出两条数据,有时候不准。
filtered的值指返回结果的行占需要读到的行(rows列的值)的百分比。
mysql> select teacher_name from teacher;
这条sql能查出三条数据。
mysql> explain select teacher_name from teacher where teacher_name = 'a';
这条sql能查出一条数据,通过explain可以看到filtered为33.33。
即filtered = 1 / 3 * 100/100 = 33.33%,保留两位小数。
但是我换一个条件查询。
mysql> explain select teacher_name from teacher where id = 1;
这个filtered是100,理论上应该也是33.33,但是因为where 条件 id是主键,或者别的索引列,它直接根据索引查找,就不需要跟
全局比较。如果索引上查找某个值,它人为你只会去查询一条数据,并且你也只能查询出一条数据,所以是100。
Extra显示额外信息,就像你做excel一样,最后搞个备注列。备注很多时候很重要,
Extra列也很重要,通常有如下几类重要信息需要关注。
1)Using index
Extra显示Using Index,说明用到了索引,是性能高的表现。一般出现在查询的列被索引列覆盖,
并且where筛选条件是索引的前导列。
mysql> explain select class_id from class_teacher where class_id = 1;
2)Using where
Extra显示Using where,表示没有用到索引,查询的列未被索引列覆盖,where筛选条件非索引的前导列。
mysql> explain select teacher_name from teacher where teacher_name = 'a';
3)Using where Using index
Extra显示Using whre Using index,表示查询的列被索引列覆盖,并且where筛选条件是索引列之一但
不是索引的前导列,说明无法直接通过索引查找查询到符合条件的数据。
mysql> explain select class_id from class_teacher where teacher_id = 1;
4)NULL
Extra显示null,表示查询的列未被索引列覆盖,并且where筛选条件是索引的前导列,说明用到了索引,
但是部分字段未被索引列覆盖,必须通过“回表”来实现,所以不是纯粹地用到了索引,也不是完全没用到索引。
mysql> explain select * from class_teacher where class_id = 1;
5)Using index condition
Extra显示Using index condition与Using where类似,查询的列不完全被索引列覆盖,where条件中是一个前导列的范围。
mysql> explain select * from class_teacher where class_id > 1;
6)Using temporary
Extra显示Using temporaty表示MySql需要创建一张临时表来处理查询。
中间MySql处理过程需要多处理一个临时表,一般这种情况是需要优化处理的。
优化处理一般使用索引优化手段会多些。
在teacher.teacher_name没有索引,如果对该字段去重处理distinct,则MySql会把查询结果集建
临时表,然后再去重处理,中间多了创建临时表的过程,效率低;
mysql> explain select distinct teacher_name from teacher;
咱们在开始建表的时候,做了一个class.class_name建立了idx_class_name索引,
通过class的class_name建立索引类比teacher上没有建立索引的效果。
mysql> explain select distinct class_name from class;
从比较结果知道,name上建立索引的distinct会走索引去重,而不会创建临时表,索引效率高。
7)Using filesort
Extra显示Using filesort表示MySql会对查询结果使用外部索引排序,
而没有按索引次序从表里读取行,没有用索引。此时MySql会根据联接类型扫描所有符合条件的记录,
并保存排序关键字和行指针,然后排序关键字并按顺序检索行信息。MySql绕着弯干这么多事,
效率很低,如果你的order by出现Using filesort,一般需要优化了。
在teacher.teacher_name未创建索引,会扫描teacher整个表,保存排序关键字teacher_name和对应的id,
然后排序teacher_name并检索行记录。
mysql> explain select * from teacher order by teacher_name;
在class.class_name建立了idx_class_name索引,此时查询时Extra是Using index,使用了索引列排序,
效率高效。
explain select * from class order by class_name;
1、重点关注id、type、key、Extra列,Extra最复杂。
2、explain需要在实践中不断使用,不断体会,才能自己找到精髓,与自己融为一体。
理论+实战才是唯一标准。