MySQL 8.0 能通过索引消除 Top N 查询的 Using filesort,但需索引方向、顺序、等值条件三者严格匹配;否则即使 WHERE 命中索引,ORDER BY 不复用仍触发 filesort。

MySQL 8.0 能通过索引排序消除 Top N 查询的 Using filesort,但前提是索引结构与查询的 ORDER BY + LIMIT 组合严格匹配——不是“有索引就行”,而是“方向、顺序、等值条件”三者缺一不可。
为什么加了索引还是出现 Using filesort?
常见错误是只关注 WHERE 条件是否走索引,却忽略 ORDER BY 是否能复用同一索引扫描路径。只要 EXPLAIN 的 Extra 字段里出现 Using filesort,就说明排序没下推到存储层,哪怕 key 显示命中了索引。
-
key字段只表示用了哪个索引做数据定位,不等于排序复用 - 真正判断依据是
Extra是否为空或含Using index,且type是range或更优 - MySQL 8.0.20+ 可用
EXPLAIN FORMAT=TREE直接看执行计划中有没有filesort节点
复合索引方向必须和 ORDER BY 逐列一致
例如要优化 SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 5,必须建 INDEX (user_id, created_at DESC)。
- 首列
user_id是等值条件,满足最左前缀,锁定 B+ 树某一分支 - 第二列
created_at DESC方向与ORDER BY完全一致,InnoDB 可直接按物理逆序扫描叶子节点 - 若建的是
(user_id, created_at ASC),即使WHERE命中,仍会触发filesort - 若
ORDER BY是created_at DESC, amount ASC,索引也必须写成(user_id, created_at DESC, amount ASC),少一个ASC都不行
单列 DESC 索引对 Top N 几乎无效
建 INDEX (updated_at DESC),再查 WHERE updated_at > '2025-01-01' ORDER BY updated_at DESC LIMIT 10,实际常退化为全索引扫描,而非高效截断。
- 原因:B+ 树对单列降序索引做范围扫描时,物理存储顺序与扫描方向冲突,无法提前终止
- 真正有效的模式是“高选择性等值条件 + 降序列”,比如
WHERE status = 'shipped' AND updated_at > '2025-01-01'配INDEX (status, updated_at DESC) - 单独建
DESC索引,90% 场景只是浪费空间、拖慢写入,却不提升 Top N 性能
ROW_NUMBER() 不等于索引优化,它依赖索引但不替代索引
写 ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) 时,如果没在 (category, sales DESC) 上建联合索引,OVER 子句内部排序仍会触发 Using filesort,甚至生成磁盘临时表。
- 窗口函数本身不自动创建索引,它只是语法糖,底层仍靠索引支撑排序
-
PARTITION BY和ORDER BY字段必须联合建索引,且方向严格对应 - 别漏掉外层过滤:窗口结果集仍是全量行,
WHERE row_num 必须套在子查询/CTE 外层
真正起作用的从来不是“用了 DESC”这个关键字,而是等值条件能否锚定索引分支、后续列方向能否让 InnoDB 按需正向或反向遍历叶子链表——这两点卡住,再多的 DESC 也只是摆设。


















