使用explain关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈在 select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是执行这条SQL。
通过explain执行SQL语句还能查看优化器优化之后的SQL,紧随其后通过 SHOW WARNINGS;
命令可以得到优化后的查询语句,从而看出优化器优化了什么
# 例如
EXPLAIN
SELECT * FROM role;
SHOW WARNINGS;
这里会使用角色加菜单关系三张表来做演示。
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`del_flag` tinyint(4) NULL DEFAULT 0 COMMENT '删除标识 0:已删除 1:未删除 默认0',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT = '菜单表';
INSERT INTO `menu` VALUES (1, '系统管理', '管理系统用户角色菜单相关', 0, '2023-09-28 10:45:41');
INSERT INTO `menu` VALUES (2, '商品管理', '管理系统商品', 0, '2023-09-28 10:46:04');
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(50) DEFAULT NULL COMMENT '角色名称',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`del_flag` tinyint(4) NULL DEFAULT 0 COMMENT '删除标识 0:已删除 1:未删除 默认0',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_roleName` (`role_name`)
) ENGINE = InnoDB COMMENT = '角色表';
INSERT INTO `role` VALUES (1, '超级管理员', '最大角色', 0, '2023-09-28 10:43:31');
INSERT INTO `role` VALUES (2, '普通人员', '小卡拉米', 0, '2023-09-28 10:45:14');
DROP TABLE IF EXISTS `role_menu`;
CREATE TABLE `role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色菜单关联ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_roleId_menuId` (`role_id`,`menu_id`)
) ENGINE = InnoDB COMMENT = '角色菜单关联表';
INSERT INTO `role_menu` VALUES (1, 1, 1);
INSERT INTO `role_menu` VALUES (2, 1, 2);
INSERT INTO `role_menu` VALUES (3, 2, 2);
explain中有12个列分别代表不同指标,这里会举例介绍。
id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增长的,id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。
select_type 表示对应行是简单还是复杂的查询。
EXPLAIN SELECT * FROM role WHERE id = 1;
EXPLAIN SELECT t1.id,(SELECT menu_name FROM menu WHERE id=t1.menu_id ) AS menuName FROM role_menu t1
EXPLAIN SELECT * FROM role WHERE id=1 UNION SELECT * FROM role WHERE id=2;
这一列表示 explain 的一行正在访问哪个表,如果表设置了别名会显示表的别名。
指分区信息。数据库优化有分库、分表、分区;这里的分区值表的分区信息。
这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
一般来说,得保证查询达到range级别,最好达到ref
mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表。
EXPLAIN SELECT MIN(id) FROM role;
system是const的特例,表里只有一条元组匹配时为system,可以看SHOW WARNINGS;
的结果。
EXPLAIN SELECT * FROM (SELECT '666' AS num FROM dual) t1;
SHOW WARNINGS;
mysql能对查询的某部分进行优化并将其转化成一个常量(可以看SHOW WARNINGS;
的结果)。用于primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。
EXPLAIN SELECT * FROM role WHERE id=1;
SHOW WARNINGS;
primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。
EXPLAIN
SELECT * FROM role t1 INNER JOIN role_menu t2 ON t1.id=t2.id where t1.id;
相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
EXPLAIN SELECT * FROM role where role_name = '超级管理员';
范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。
EXPLAIN SELECT * FROM role where id < 3;
扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些。
EXPLAIN SELECT role_name FROM role;
即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。
EXPLAIN SELECT * FROM role WHERE del_flag = 1;
这一列显示查询可能使用哪些索引来查找。
这一列显示mysql实际采用哪个索引来优化对该表的访问。
# 强制使用idx_roleName索引
# 需要注意的就算我们指定了需要使用的索引,但是MySQL优化器觉得全表扫描更加高效或者我们指定的索引查询条件无法使用到,还是可能会忽略我们指定的索引。
EXPLAIN SELECT * FROM role FORCE INDEX(idx_roleName) WHERE role_name = '超级管理员';
# 忽略使用idx_roleName索引
EXPLAIN SELECT * FROM role IGNORE INDEX(idx_roleName) WHERE role_name = '超级管理员';
这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。
举例来说,role_menu的联合索引 idx_roleId_menuId由 role_id 和 menu_id 两个bigint列组成,并且每个bigint是8字节。通
过结果中的key_len=8可推断出查询使用了第一个列:role_id 列来执行索引查找。
EXPLAIN SELECT * FROM role_menu WHERE role_id = 1;
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:role.id)
这一列是MySQL估计要读取并检测的行数,注意这个不是结果集里的行数,这个只是预估值和实际值可能有偏差。
filtered指标表示扫描行数和条件过滤结果集的百分比,比如我们这边查询 role_name = ‘超级管理员’,全表扫描总扫描数据集2条过滤出1条得到比值50%。
EXPLAIN SELECT * FROM role WHERE role_name = '超级管理员';
这一列展示的是额外信息。常见的重要值如下:
使用覆盖索引,mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值
EXPLAIN SELECT role_name FROM role WHERE role_name = '超级管理员';
使用 where 语句来处理结果,并且查询的列未全部被索引覆盖
EXPLAIN SELECT * FROM role WHERE role_name = "超级管理员" AND remark = "最大角色";
尝试只使用索引来获取数据,即能用索引就用;
EXPLAIN SELECT * FROM role WHERE role_name > '超级管理员';
用临时表存储中间结果,常用于DISTINCT、GROUP BY、ORDER BY 等操作。
EXPLAIN SELECT DISTINCT remark FROM role;
将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的。
EXPLAIN SELECT * FROM role ORDER BY create_time;
使用某些聚合函数(比如 max、min)来访问存在索引的某个字段。
EXPLAIN SELECT MIN(id) FROM role;