看完这篇文章,99%的人都会使用Mysql Explain工具

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列是 <derivenN> 格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询。
当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select 行id。

具体例子可参考,上面第二张图的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,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行记录。

  1. 简单 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)来访问存在索引的某个字段
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342