desc索引仅在order by方向与索引定义逐列严格一致且where满足最左前缀时才跳过filesort;单列desc索引对纯order by created_at desc查询常因优化器不选反向扫描而失效,带范围条件更会完全失效;真实分页需联合索引如index(status, created_at desc),并补唯一列(如id)防重复。

DESC 索引不是分页乱序或慢的万能解药,它只在 ORDER BY 方向与索引定义逐列严格一致 + WHERE 满足最左前缀 时才能跳过 filesort;否则你建的 INDEX (created_at DESC) 和没建几乎一样。
为什么加了 INDEX (created_at DESC) 还是 Using filesort
这是最常被误判的点:单列降序索引对纯 ORDER BY created_at DESC LIMIT 20 查询,在多数情况下根本用不上排序能力。
- MySQL 8.0 对单列
DESC索引执行倒序扫描时,实际走的是Backward index scan(反向遍历 B+ 树),但优化器往往不选它——尤其当表数据量不大、或统计信息不准时,它宁可全表扫 +filesort -
EXPLAIN中出现Extra: Using filesort就说明没走索引排序;哪怕key字段显示你建的索引名,只要Extra里有这个字样,就等于白建 - 更隐蔽的坑:
WHERE created_at > '2025-01-01'这类范围条件,会直接让单列DESC索引失效于排序——B+ 树无法同时满足“范围过滤”和“倒序读取”的物理顺序要求
INDEX (status, created_at DESC) 才是分页场景的正确姿势
真实业务分页几乎都带过滤条件,比如 WHERE status = 'done' ORDER BY created_at DESC LIMIT 20。这时必须把等值列放前面、排序列放后面,并明确标 DESC。
- 索引定义必须和查询完全对齐:
INDEX (status ASC, created_at DESC)→ 只加速WHERE status = ? ORDER BY created_at DESC;换成ORDER BY created_at ASC或漏掉status = ?,索引就退化为普通查找 - 验证是否生效,盯紧三处:
key显示该索引名、type是ref或range、Extra里没有Using filesort(最好还有Using index) - 如果
WHERE条件含IN或多个等值项(如status IN ('done','pending')),MySQL 8.0.20+ 才较稳定支持该索引用于排序;低版本可能仍触发filesort
对比执行计划:一眼识别降序索引是否真起作用
别只看 key 字段,重点比对 Extra 和 rows 的变化。
- 建索引前:
type: ALL或type: index,rows≈ 表总行数,Extra: Using filesort - 建
INDEX (status, created_at DESC)后:type: ref,rows缩小到匹配status的行数,Extra变成Using index(无filesort)——这才是有效命中 - 用
EXPLAIN FORMAT=TREE更直观:能看到-> Sort row sources: false,表示跳过了排序节点;若看到-> Sort,说明还是在内存/磁盘里排
分页稳定性比速度更关键:别忘了唯一性补丁
即使 INDEX (status, created_at DESC) 让排序变快,ORDER BY created_at DESC LIMIT 20,10 仍可能跨页重复——因为多个记录的 created_at 完全相同。
- 解决方法不是加更多索引,而是补上唯一列:
ORDER BY created_at DESC, id DESC,并确保索引包含id(如INDEX (status, created_at DESC, id DESC)) - 否则第一页最后一条和第二页第一条时间戳一样,数据库按任意顺序返回,前端就看到重复或漏数据
- 这个细节在压测和线上灰度时才暴露,但修复成本极低:改 SQL + 扩展索引,无需动业务逻辑











