加了 index (created_at desc) 却仍 using filesort,说明索引未用于排序;降序索引仅在等值过滤(如 where status = 'done')且 order by 方向与索引逐列严格一致(如 order by status, created_at desc)时才跳过 filesort,单列 desc 索引对纯倒序分页基本无效。

加了 INDEX (created_at DESC) 却还是 Using filesort,说明索引根本没用于排序——降序索引不是建了就生效,它只在「等值过滤 + 方向严格匹配」的组合下才跳过排序。
为什么单列 DESC 索引对倒序分页基本无效
纯 ORDER BY created_at DESC LIMIT 20 查询,即使建了 INDEX (created_at DESC),优化器大概率仍选全表扫描或聚簇索引扫描,而非反向遍历该索引。原因很实际:没有 WHERE 条件锚定起点,B+ 树倒序扫描的 I/O 成本未必优于正向扫主键 + 内存排序。
- EXPLAIN 中
type是ALL或index,且Extra含Using filesort→ 白建 - MySQL 8.0.12+ 才真正存储物理降序键值;更早版本会静默忽略
DESC,SHOW CREATE TABLE里看不到DESC字样就是没生效 - 单列降序索引遇上范围条件(如
WHERE created_at > '2025-01-01')直接失效:B+ 树无法同时满足“范围定位”和“倒序读取”的物理顺序
WHERE status = ? ORDER BY created_at DESC 必须配联合索引
真实分页几乎都带状态过滤,这时必须把等值列放前、排序列放后,并显式声明方向:INDEX (status ASC, created_at DESC)。这个索引只加速 WHERE status = 'done' ORDER BY created_at DESC 这一种组合,换方向或漏条件就退化。
- 方向必须逐列一致:
INDEX (status, created_at DESC)不支持ORDER BY created_at DESC单独用,也不支持ORDER BY status DESC, created_at DESC -
WHERE status IN ('done', 'pending')在 MySQL 8.0.20+ 才较稳定启用排序能力;低版本可能仍触发Using filesort - 若查询含
SELECT *,回表后结果不再有序,MySQL 会强制补排序——优先用SELECT id, status, created_at配合覆盖索引
如何验证降序索引真正在工作
别只盯 key 字段是否命中索引名,关键看三处:是否跳过排序、是否缩小扫描行数、是否避免回表。
-
Extra字段必须不含Using filesort,最好还有Using index(覆盖索引) -
type应为ref或range,rows值应明显小于总行数(比如从百万级降到几千) - MySQL 8.0.20+ 可用
EXPLAIN FORMAT=TREE,直接看执行计划中是否有-> Sort节点;没有则确认排序已下推 - 用
sys.schema_unused_indexes定期清理长期未命中的降序索引——它比升序索引占更多空间,维护成本更高
最易被忽略的一点:降序索引的物理存储与升序不兼容,INDEX (a ASC) 和 INDEX (a DESC) 是两份独立数据。别指望一个索引覆盖多种排序需求,每个独特 ORDER BY 组合都需要对应设计,且必须以等值条件为前提。











