连载中 2/22

EXPLAIN 精读:type 等级、rows 与 Extra 的暗语

2026-05-01 · 5113 阅读 · 0 评论 · 0 赞

EXPLAIN 的每个字段都是证词

上一篇我们用 EXPLAIN 抓住了那条 8 秒的慢 SQL,但只挑了五个列粗讲。评论区有人追问:Using index 和 Using index condition 到底差在哪?key_len 那串数字怎么读?这篇就把执行计划表逐列精读——EXPLAIN 的每一列都是执行引擎的证词,会读证词才能断案。

id 与 select_type:谁先执行

多表 JOIN 和子查询的执行计划会有多行,先看谁先跑:id 相同的行从上往下执行;id 不同的,数字大的先执行(子查询先跑)。select_type 标记每行的角色:SIMPLE 是无子查询的普通查询,PRIMARY 是最外层,DERIVED 是派生表(FROM 里的子查询物化出来的临时表),DEPENDENT SUBQUERY 是依赖外层的子查询——最后这个要格外留神,它可能对外层的每一行都执行一次。

type:七档访问等级

type 是执行计划里最先看的列,按性能从好到差分档:

type含义典型场景
const主键或唯一索引等值查询,最多一行WHERE id = 9527
eq_refJOIN 时用被驱动表的主键/唯一索引,最多匹配一行JOIN users u ON o.user_id = u.id
ref普通索引等值查询,可能多行WHERE user_id = 9527
range索引范围扫描WHERE create_time > 某时刻
index扫整棵索引树,比 ALL 好在不用回表但数据没少扫SELECT count(1) 全索引扫
ALL全表扫描没有可用索引

经验线:线上查询至少要到 range,最好 ref 及以上;core 链路上的 SQL 出现 ALL,基本可以直接立个案。补充一个冷知识:system 是 const 的特例(表只有一行),实际几乎见不到。

key_len:联合索引用到了第几列

key_len 最容易被忽略,却最有用——它告诉你联合索引实际用了几列。算式很简单:列的基础长度 + 1 字节 NULL 标记(列允许 NULL 才有)+ 变长列的 1-2 字节长度前缀。

  • bigint:8 字节;int:4 字节。
  • varchar(n) utf8mb4:4n + 2(长度前缀)+ 1(NULL 标记,可空才有)。varchar(50) 可空列就是 4×50+2+1 = 203 字节。
  • datetime:5 字节 + 小数秒精度,datetime(0) 不带 NULL 标记就是 5,datetime(3) 是 8。

拿上一篇的 idx_user_status_time (user_id, status, create_time) 验算:user_id bigint not null = 8,status tinyint not null = 1,create_time datetime(3) not null = 8(5+3)。计划里 key_len 显示 9,就是只用到 user_id + status 两列;显示 17 才是三列全用上。ORDER BY 能否免排序,取决于联合索引用到哪一列——这正是下一篇最左前缀的主题。

rows 与 filtered:成本估算

rows 是优化器估算的扫描行数,基于统计信息,不是精确值(下一篇讲它什么时候不准)。filtered 是这批扫描结果里预计满足剩余条件的百分比:rows=381 万、filtered=0.001,意味着最终约 42 行。rows × filtered 就是驱动下一张表(JOIN)的行数,JOIN 越靠前的表被扫描越多——小表驱动大表说的就是让 rows 小的表当驱动表。

Extra:附加动作的暗语

取值含义与对策
Using index覆盖索引:查询的列全在索引里,不用回表。好信号。
Using index condition索引下推(ICP):把 WHERE 条件下推到引擎层在索引上先过滤,减少回表次数。5.6+ 的优化。
Using where服务层过滤。出现在 range 之后属正常,出现在 ALL 上就要留意。
Using filesort需要额外排序(内存装不下还会落盘)。ORDER BY 没吃到索引有序性,重点优化对象。
Using temporary用临时表处理(常见于 GROUP BY、DISTINCT 没吃到索引)。比 filesort 更糟。
Using join bufferJOIN 没吃到索引,驱动表在 buffer 里逐块匹配被驱动表。给关联字段加索引。

回应开头的问题:Using index 是覆盖索引(整个查询不回表),Using index condition 是索引下推(还是要回表,只是回表次数变少)——名字像,完全两码事。

8.0 彩蛋:EXPLAIN ANALYZE

EXPLAIN 给的是预估,MySQL 8.0.18 起多了一个 EXPLAIN ANALYZE:真的执行一遍,返回每一步的实际耗时和实际行数。预估与实际偏差大时,说明统计信息该更新了(ANALYZE TABLE)——这正是优化器篇的伏笔。

到这,EXPLAIN 的证词读全了。但还有个根本问题没回答:索引凭什么让 381 万行变 42 行?它的数据结构长什么样、为什么偏偏是 B+ 树?下一篇从页和扇出讲起。

咖啡凉了,记得趁热喝。

503

10 年全栈工程师 · 503咖啡馆主理人

#MySQL#EXPLAIN#执行计划#索引#性能优化

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞