Bad plan · 仅有 user_id 单列索引
当前展示- key
- idx_user_id
- possible_keys
- idx_user_id
- rows
- ≈ 8000
- filtered
- 10%
- Extra
- Using where; Using filesort
先扫描该用户全部订单 (~8000 行),Server 层按 status 过滤,最后在内存里按 created_at 排序。
参考:高性能 MySQL(第 4 版) · MySQL 技术内幕:InnoDB 存储引擎(第 2 版) · MySQL 8.4 Reference Manual。每章含可暂停 Lab 动画 + 面试问答 + 书籍章节对照。
MySQL 面试 · Lab D · SQL 优化与 EXPLAIN
主问题
EXPLAIN SELECT * FROM orders
WHERE user_id = 1001 AND status = 'PAID'
ORDER BY created_at DESC LIMIT 20;Extra 出现 Using filesort,你怎么读、怎么优化?
Using filesort 说明 ORDER BY created_at 没走索引顺序、需要额外排序。
建一个 (user_id, status, created_at) 联合索引, 等值过滤后正好按 created_at 有序读取。
排序由索引顺序提供,filesort 就消失了。
点节点跳转 · 滚动时自动高亮当前位置
表面问「Using filesort 怎么优化」,实际是在问:你能不能把 EXPLAIN翻译成访问路径,再闭环到索引设计。
EXPLAIN SELECT * FROM orders WHERE user_id = 1001 AND status = 'PAID' ORDER BY created_at DESC LIMIT 20;
两个等值过滤 + 一个排序。痛点在 Extra 的 Using filesort——排序没能靠索引顺序完成。
别逐字段背,抓住这三组,就能把计划翻译成访问路径。
type & key
访问方式 + 实际索引
type 反映访问方式好坏(const/ref/range/ALL);key 是优化器最终选的索引,不等于 possible_keys。
rows & filtered
估算扫描 + 过滤比例
都是优化器估算。rows×filtered/100 才是估算过滤后行数,实际返回还要看 LIMIT。
Extra
执行细节信号
Using index = 覆盖;Using filesort = 排序没走索引;Using temporary = 用了临时表。
这两个都是优化器的估算,不是真实返回行数。
切换「优化前 / 优化后」,逐步对照同一条 SQL 的两份执行计划。
SELECT * FROM orders WHERE user_id = 1001 AND status = 'PAID' ORDER BY created_at DESC LIMIT 20;
先扫描该用户全部订单 (~8000 行),Server 层按 status 过滤,最后在内存里按 created_at 排序。
联合索引按 (user_id, status) 定位到子树,叶子已按 created_at DESC 排好,顺序取前 20 条。
EXPLAIN 的 rows 是优化器估算扫描行数,不是查询最终返回的 20 行。LIMIT 命中的实际返回行数靠 LIMIT 子句和真实数据决定。
只要 SELECT 的列没有全部出现在二级索引里,就会回表到聚簇索引取整行。Extra 里出现Using index才是真正的覆盖索引。
当前发生了什么
慢查询定位到这条 SQL,先用 EXPLAIN 看执行计划,而不是凭感觉加索引。
为什么会这样
优化的第一步是读懂优化器打算怎么访问数据。
面试怎么回答
我会先从慢日志 / APM 定位具体 SQL 和时间窗口,再 EXPLAIN。
常见误区
误区:一看慢就加索引,不先看执行计划。
把等值列放前、排序列放最后,让一个索引同时管过滤和排序。
优化前Using filesort优化后索引提供顺序四个最常见的 Extra 信号,记住「好 / 中性 / 想消掉」。
| Extra 信号 | 含义 | 好还是坏 |
|---|---|---|
| Using index | 覆盖索引,免回表 | 好 |
| Using where | Server 层还要再过滤一遍 | 中性 |
| Using filesort | 排序没法靠索引顺序完成 | 想办法消掉 |
| Using temporary | 用了临时表(GROUP BY / DISTINCT / 排序) | 较重,尽量避免 |
按时间预算选一档念出来。
Using filesort 说明 ORDER BY created_at 没走索引顺序、需要额外排序。
建一个 (user_id, status, created_at) 联合索引,让等值过滤后正好按 created_at 有序读取,filesort 就消失了。
勾掉每一条,这道题就算通关。