Explain工具介绍
EXPLAIN是MySQl必不可少的一个分析工具,主要用来测试sql语句的性能及对sql语句的优化,或者说模拟优化器执行SQL语句。
注意:本文基于mysql5.7进行操作,各个版本的mysql使用Explan会有微小的差异
前期掌握点:
什么是mysql的二级索引
mysql中每个表都有一个聚簇索引(clustered index ),除此之外的表上的每个非聚簇索引都是二级索引,又叫辅助索引(secondary indexes)。
以InnoDB来说,每个InnoDB表具有一个特殊的索引称为聚集索引。如果您的表上定义有主键,该主键索引是聚集索引。如果你不定义为您的表的主键 时,MySQL取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚集索引。如果没有这样的列,InnoDB就自己产生一个这样的ID值,它有六个字节,而且是隐藏的,使其作为聚簇索引。
示例表
-- ----------------------------
-- Table structure for order
-- ----------------------------
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of order
-- ----------------------------
INSERT INTO `order` VALUES (2, '华为电脑');
INSERT INTO `order` VALUES (1, '小米电脑');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '黎明1', 25, '2020-09-09 22:49:08', '2020-09-09 22:49:10');
INSERT INTO `user` VALUES (2, '黎明2', 25, '2020-09-09 22:49:20', '2020-09-09 22:49:22');
INSERT INTO `user` VALUES (3, '黎明3', 25, '2020-09-09 22:49:29', '2020-09-09 22:49:30');
-- ----------------------------
-- Table structure for user_order
-- ----------------------------
DROP TABLE IF EXISTS `user_order`;
CREATE TABLE `user_order` (
`id` int(11) NOT NULL,
`oid` int(11) NOT NULL,
`uid` int(11) NOT NULL,
`ramark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_order_id`(`oid`, `uid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_order
-- ----------------------------
INSERT INTO `user_order` VALUES (1, 1, 1, NULL);
INSERT INTO `user_order` VALUES (2, 1, 2, NULL);
INSERT INTO `user_order` VALUES (3, 2, 1, NULL);
接下来开始对explan结果集列详细解释
EXPLAIN select * from `order` where id = 2
1.id列
id列的编号是 select 的序列号,有几个select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。 id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。
2.select_type列
select_type:表示对应行是简单查询还是复杂查询
- simple:简单查询。查询不包含子查询和union
- primary:复杂查询中最外层的 select
- subquery:包含在 select 中的子查询(不在 from 子句中)
- derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义)
- union:在 union 中的第二个和随后的所有select
下面这个例子主要来了解(simple
)查询类型
EXPLAIN select * from `order` where id = 1
下面这个例子主要来了解(primary、subquery、derived
)几种类型
set session optimizer_switch='derived_merge=off'; #关闭mysql5.7新特性对衍生表的合并优化
explain select (select 1 from user where id = 1) from (select * from `order` where id = 1) ud;
其实在这里我们可以看到id列,id值最大先执行,那么肯定是先执行是派生表查询,接着第二步执行子查询,最后是最外层的select查询
下面这个例子主要来了解(union)查询类型
explain select * from user union all select * from `order`
set session optimizer_switch='derived_merge=on'; #还原默认配置
3.table列
这一列表示 explain 的一行正在访问哪个表。
当 from 子句中有子查询时,table列是
当有 union 时,UNION RESULT 的 table 列的值为
具体例子可参考,上面第二张图的table列
4.type列
这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL (system效率最高,All效率最低)
一般来说,得保证查询达到range级别,最好达到ref
NULL:
mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。
例如:在索引列中选取最小值,可以单独查找索引树来完成,不需要在执行时访问表(简单来说,表的数据是以b+树格式存储的,而且叶子节点的data值是有序的,那么通过Min函数,可以直接再索引里面找到最小的值,无需查询表了,所以这里的type才会为null,所以这种性能是非常高的)
explain select min(id) from user
const, system:
mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。
用于 primary key (主键索引)或 unique key (唯一索引) 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是const的特例,表里只有一条元组匹配时为system
--执行如下语句,结果要和图中一样的话, #得先关闭mysql5.7新特性对衍生表的合并优化 set session optimizer_switch='derived_merge=off'; 还原配置:set session optimizer_switch='derived_merge=on';
explain select * from (select * from `order` where id = 1) tmp;
关于下图中,给出优化的sql,显示查询的是dual表,这个是mysql的自带的空表,像我们上面的sql,mysql会直接将数据解析到空表中,查询返回出去,所以效率才会高
eq_ref:
primary key (主键索引) 或 unique key (唯一索引) 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type
explain select * from user_order left join `order` on user_order.oid = `order`.id
ref:
相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行记录。
- 简单 select 查询,name是普通索引(非唯一索引)
explain select * from `order` where name = '小米电脑';
2.关联表查询,idx_user_order_id是oid和uid的联合索引,这里使用到了user_order的左边前缀oid部分。(这里底层是使用了覆盖索引查询,因为我的连表查询条件中,两个id都是被索引关联着,这样的话,可以直接通过辅助索引就能定位到记录,而不需要再通过辅助索引的id再去主键索引中查询其他的记录了)
explain select oid from `order` left join user_order on `order`.id = user_order.oid
range:
范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行记录。
explain select * from user where id > 1;
index:
扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般未使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些。
explain select * from `order`;
ALL:即全表扫描,扫描你的聚簇索引(什么是聚簇索引文章开头有解释)的所有叶子节点。通常情况下这需要增加索引来进行优化了。
explain select * from user;
5.possible_keys列
这一列显示查询可能使用哪些索引来查找。
6.key列
这一列显示mysql实际采用哪个索引来优化对该表的访问。
7.key_len列
这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。 举例来说,film_actor的联合索引 idx_user_order_id 由 oid 和 uid 两个int列组成,并且每个int是4字节。通 过结果中的key_len=4可推断出查询使用了第一个列:uid列来执行索引查找。
explain select * from user_order where oid = 2;
8.ref列
这一列显示了在key列记录的索引中,表查找值所用到的列或常量
9.rows列
这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。
10.Extra列
这一列展示的是额外信息。常见的重要值如下:
- Using index:使用覆盖索引
- Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖(简单理解为where条件查询的字段没有添加索引)
- Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围;
- Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索 引来优化。
- Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一 般也是要考虑使用索引来优化的。
- Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段