Mysql的执行计划解析(全字段)

执行计划解析(全字段)

使用执行计划 EXPLAIN 或者 DESC

EXPLAIN select * from user

可以得到以下返回结果

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEuserALL10651100

这些字段的含义分别是

  • id: 在一个大的查询语句中每个 SELECT 关键字都对应一个唯一的 id
  • select_type: SELECT 关键字对应的那个查询的类型
  • table :表名
  • partitions :匹配的分区信息
  • type :针对单表的访问方法
  • possible_keys :可能用到的索引
  • key :实际上使用的索引
  • key_len :实际使用到的索引长度
  • ref :当使用索引列等值查询时,与索引列进行等值匹配的对象信息
  • rows :预估的需要读取的记录条数
  • filtered :某个表经过搜索条件过滤后剩余记录条数的百分比
  • Extra : — 些额外的信息

用例表(注意!表中数据量会对执行计划造成影响,具体影响由 sql执行成本来决定,本文暂不讨论此块内容)

CREATE TABLE `s1`  (
  `id` bigint(22) NOT NULL AUTO_INCREMENT COMMENT '订单的主键',
  `order_no` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单的编号',
  `order_note` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '订单的说明',
  `insert_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '插入订单的时间',
  `expire_duration` bigint(22) NOT NULL COMMENT '订单的过期时长',
  `expire_time` datetime(0) NOT NULL COMMENT '订单的过期时间',
  `order_status` smallint(6) NOT NULL DEFAULT 0 COMMENT '订单的状态',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `u_idx_day_status`(`insert_time`, `order_status`, `expire_time`) USING BTREE,
  INDEX `idx_order_no`(`order_no`) USING BTREE,
  INDEX `idx_insert_time`(`insert_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10814 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

CREATE TABLE `s2`  (
  `id` bigint(22) NOT NULL AUTO_INCREMENT COMMENT '订单的主键',
  `order_no` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单的编号',
  `order_note` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单的说明',
  `insert_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '插入订单的时间',
  `expire_duration` bigint(22) NOT NULL COMMENT '订单的过期时长',
  `expire_time` datetime(0) NOT NULL COMMENT '订单的过期时间',
  `order_status` smallint(6) NOT NULL DEFAULT 0 COMMENT '订单的状态',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `u_idx_day_status`(`insert_time`, `order_status`, `expire_time`) USING BTREE,
  INDEX `idx_order_no`(`order_no`) USING BTREE,
  INDEX `idx_insert_time`(`insert_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10814 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

id , select序号

id相同,从上往下依次执行
EXPLAIN SELECT * FROM s1 left join s2 on s1.id = s2.id;
EXPLAIN SELECT * FROM s1 left join s2 on s1.id = s2.id;


在这里插入图片描述

id不同,id越大优先级越高,越先被执行
desc SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 ) or order_no = 'a' 

在这里插入图片描述

select_type,查询语句类型

SIMPLE:简单的 select 查询,不使用 union 及子查询

在这里插入图片描述

PRIMARY:最外层的 select 查询
EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

在这里插入图片描述
从结果中可以看到,最左边的小查询 SELECT * FROMN s1 对应的是执行计划中的第一条记录,它的 select_type 值就是 PRIMARY

UNION:UNION 中的第二个或随后的 select 查询,不依赖于外部查询的结果

查询同上

对于包含 UNION 或者 UNION ALL 的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的查询的 select_type 值就是 UNION,可以对比上一个例子的效果。

UNION RESULT:UNION 结果集

MySQL 选择使用临时表来完成 UNION 查询的去重工作,针对该临时表的查
询的 select_type 就是 UNION RESULT,例子同上。

SUBQUERY:子查询中的第一个 select 查询,不依赖于外 部查询的结果集
EXPLAIN SELECT * FROM s1 WHERE id = (SELECT id FROM s2 where order_no ='a') 

在这里插入图片描述

如果包含子查询的查询语句不能够转为对应的 semi-join 的形式,并且该子查询是不相关子查询,并且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个 SELECT 关键字代表的那个查询的select_type 就是SUBQUERY

DEPENDENT UNION:UNION 中的第二个或随后的 select 查询,依赖于外部查询的结果集
DEPENDENT SUBQUERY:子查询中的第一个 select 查询,依赖于外部查询的结果集

在包含 UNION 或者 UNION ALL 的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的 select_type 的值就是 DEPENDENT UNION。比方说下边这个查询

EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 WHERE id = 123 UNION SELECT id FROM s1 WHERE id = 456);

在这里插入图片描述

上述sql似乎内部的sql没有依赖于外层查询,但实际上MySQL 优化器对 IN 操作符的优化会将 IN 中的非关联子查询优化成一个关联子查询,用了s1.id = 123 和s1.id = 456 来进行关联

DERIVED: 用于 from 子句里有子查询的情况。 MySQL 会 递归执行这些子查询, 把结果放在临时表里。

对于采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的select_type 就是 DERIVED。

子查询物化:子查询的结果通常缓存在内存或临时表中。

EXPLAIN SELECT * FROM (SELECT id, count(*) as c FROM s1 GROUP BY id) AS derived_s1 where c >1;

在这里插入图片描述

MATERIALIZED:物化子查询

当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的 select_type 属性就是 MATERIALIZED

UNCACHEABLE SUBQUERY: 结果集不能被缓存的子查询,必须重新为外层查询的每一行进行评估,出现极少。
UNCACHEABLE UNION:UNION 中的第二个或随后的 select 查询,属于不可缓存的子查询,出现极少。
explain select * from s1 where id = ( select id from s2 where order_no=@@sql_log_bin);

在这里插入图片描述

table , 查询涉及的表或衍生表

partitions , 查询涉及到的分区

一般情况下不用mysql的分区

type , 访问方法/访问类型

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
出现比较多的是 system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到 range 级别,最好能达到 ref

具体含义分析

system

当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是 system,在InnoDB是不会出现system的

const

当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是 const。因为只匹配一行数据,所以很快。

例如将主键置于 where 列表中,尽管进行了回表

EXPLAIN SELECT * FROM s1 WHERE id = 1;

​ 不过这种const 访问方法只能在主键列或者唯一二级索引列和一个常数进行等值比较时才有效,如果主键或者唯一二级索引是由多个列构成的话,组成索引的每一个列都是与常数进行等值比较时,这个 const 访问方法才有效。
​ 对于唯一二级索引来说,查询该列为 NULL 值的情况比较特殊,因为唯一二级索引列并不限制 NULL 值的数量,所以上述语句可能访问到多条记录,也就是说 is null 不可以使用 const 访问方法来执行。

eq_ref

​ 在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是 eq_ref。

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

在这里插入图片描述

ref

当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是 ref。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他属于查找和扫描的混合体

EXPLAIN SELECT * FROM s1 WHERE order_no = 'a';

在这里插入图片描述

fulltext

一般不使用全文搜索

ref_or_null

有时候我们不仅想找出某个二级索引列的值等于某个常数的记录,还想把该列的值为 NULL 的记录也找出来,就像下边这个查询

-- 新加表 order_no允许为null
CREATE TABLE `order_exp_cut`  (
  `id` bigint(22) NOT NULL AUTO_INCREMENT COMMENT '订单的主键',
  `order_no` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '订单的编号',
  `order_note` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单的说明',
  `insert_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '插入订单的时间',
  `expire_duration` bigint(22) NOT NULL COMMENT '订单的过期时长',
  `expire_time` datetime(0) NOT NULL COMMENT '订单的过期时间',
  `order_status` smallint(6) NOT NULL DEFAULT 0 COMMENT '订单的状态',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `u_idx_day_status`(`insert_time`, `order_status`, `expire_time`) USING BTREE,
  INDEX `idx_order_no`(`order_no`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10819 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

EXPLAIN SELECT * FROM order_exp_cut WHERE order_no= 'abc' OR order_no IS NULL;

在这里插入图片描述

index_merge

一般情况下对于某个表的查询只能使用到一个索引,在某些场景下可以使用索引合并的方式来执行查询

EXPLAIN SELECT * FROM s1 WHERE order_no = 'a' OR insert_time = '2021-03-22 18:36:47';

在这里插入图片描述

unique_subquery

两表连接中被驱动表的 eg_ref 访问方法,unique _subquery 是针对在
一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS
子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划
的 type 列的值就是 unique_subquery

EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 where
s1.insert_time = s2.insert_time) OR order_no = 'a';

在这里插入图片描述

index_subquery

index_subquery 与 unique_subquery 类似,只不过访问⼦查询中的表时使⽤的是普通的索引

EXPLAIN SELECT * FROM s1 WHERE order_no IN (SELECT order_no FROM s2
where s1.insert_time = s2.insert_time) OR order_no = 'a';

在这里插入图片描述

range

如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法,一般就是在你的 where 语句中出现了 between、<、>、in 等的查询。
这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。此处所说的使用索引进行范围匹配中的 索引 可以是聚簇索引,也可以是二级索引

EXPLAIN SELECT * FROM s1 WHERE order_no IN ('a', 'b', 'c');

在这里插入图片描述

index

当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是 index

EXPLAIN SELECT insert_time FROM s1 WHERE expire_time = '2021-03-22 18:36:47';

在这里插入图片描述

all

最后也是最差的全表扫描,将遍历全表以找到匹配的行,这个就不做演示了。

possible_keys 与 key

表示在执行计划中,possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些,key 列表示实际用到的索引有哪些,如果为 NULL,则没有使用索引。

EXPLAIN SELECT insert_time FROM s1 WHERE expire_time = '2021-03-22 18:36:47';

在这里插入图片描述
不过上述查询中因为用到覆盖索引,并且查询条件也在索引中,因此还是用到了索引

ref

当使用索引列等值匹配的条件去执行查询时,ref 列展示的就是与索引列作等值匹配的是谁,表示逐个比较列,或者func表示使用函数了

rows

如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows 列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows 列就代表预计扫描的索引记录行数。

filtered

查询优化器预测有多少百分比的记录将会满⾜其余的搜索条件,在join时,表示驱动表对被驱动表需要链接的查询百分比

Extra

用来说明一些额外信息的,这里内容很多,取一些代表性的说明

Impossible WHERE

​ WHERE 子句永远为 FALSE 时将会提示该额外信息

Using index

当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下,在 Extra 列将会提示该额外信息。不需要回表操作。

Using index condition

虽然出现了索引列,但却不能使用到索引。虽然这么说,但这里还有个mysql优化的操作,索引下推。

EXPLAIN SELECT * FROM s1 WHERE order_no > 'z' AND order_no LIKE '%a';

比如上面的sql,会按照如下步骤执行

  • 先根据 order_no> 'z’这个条件,从二级索引 idx_order_no 中获取到对应的二级索引记录。
  • 然后按照常规步骤,由于LIKE ‘%a’ 条件不能组成范围区间而无法使用索引的,并且是select * 查询,那就会进行回表操作,取出完整记录
  • 最后检查记录是否符合LIKE ‘%a’ 条件,加入结果集或者丢弃。

但是真的不能使用索引么?并没有。mysql在第二步进行了优化,由于order_no LIKE '%a’也是属于idx_order_no 索引中的字段,只是没法通过区间取过滤数据。但是判定还是可以先用上的,步骤如下

  • 先根据 order_no> 'z’这个条件,从二级索引 idx_order_no 中获取到对应的二级索引记录。
  • 然后先不回表,通过索引取过滤 order_no LIKE '%a’这个条件,如果满足条件则回表,不满足则不回表

这样又避免了多余的回表操作。这个改进称之为索引条件下推

Using where

使用全表扫描或者索引访问来执行对某个表的查询,并且该语句的 WHERE 子句中有针对该表的搜索条件时,表示 MySQL 使用 where 子句中的条件对记录进行了过滤。

Not exists

使用左(外)连接时,如果 WHERE 子句中包含要求被驱动表的某个列等于 NULL 值的搜索条件,而且那个列又是不允许存储 NULL 值的。

Using filesort

很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的 Extra 列中显示 Using filesort。

当查询记录较多的时候要避免文件排序,尽量使用索引排序

Using temporary

MySQL 可能会借助临时表来完成一些功能,比如去重、排序之类的,如果不能有效利用索引来完成查询,MySQL 很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的 Extra 列将会显示 Using temporary 提示

参考资料

MYSQL官方文档链接: MYSQL官方文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL执行计划是指在数据库中执行SQL语句时,系统生成的关于查询执行情况的详细信息。执行计划可以告诉我们SQL语句的执行顺序、使用的索引、访问的表和字段等信息,对于SQL性能分析和优化非常有帮助。\[1\]\[2\] 要查看MySQL执行计划,可以使用MySQL提供的一套工具。执行计划可以通过解析SQL语句并生成查询计划来获取。查询计划是一个树状结构,它描述了查询的执行顺序和访问路径。可以使用EXPLAIN关键字来获取查询计划,它会返回一个结果集,其中包含了查询的执行计划信息。\[2\] 执行计划中的一些重要信息包括索引的使用情况、表的访问顺序、连接类型、扫描行数等。通过分析执行计划,我们可以了解查询的性能瓶颈所在,进而进行优化。\[1\] 需要注意的是,执行计划是根据当前数据库的统计信息和索引状态生成的,因此在进行性能分析和优化时,应该保证统计信息的准确性,并且在优化后重新生成执行计划进行验证。\[1\]\[2\] 参考资料: \[1\] 《MySQL是怎样运行的》,小孩子著 \[2\] https://blog.csdn.net/Hi_Red_Beetle/article/details/88778731 \[3\] https://blog.csdn.net/weixin_41558728/article/details/81704916 #### 引用[.reference_title] - *1* *3* [数据库系列之MySQL中的执行计划](https://blog.csdn.net/solihawk/article/details/120756584)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [详解mysql执行计划](https://blog.csdn.net/qq_43418737/article/details/121963709)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值