首页/数据/数据库速查/ORDER BY 与索引(消除 filesort)
性能与调优57 条

ORDER BY 与索引(消除 filesort)

排序列与条件列同属一个索引且顺序吻合时,B+ 树天然有序可免排序;升降序混用需 8.0 降序索引支持。

数据库速查数据更新于 2026-10-13

ORDER BY 与索引是什么

排序是昂贵操作:当排序列与条件列同属一个索引且顺序吻合时,B+ 树本身就是有序的,可以直接顺序扫描而不额外排序;否则就要取出全部结果再排(多出排序步骤)。升降序混用需要 8.0 的降序索引支持,低版本只能靠调整索引或改写语句。

基本信息

基本规则等值条件列在前,排序列紧随其后,顺序要一致
判断依据执行计划附加信息里是否出现排序标记
降序索引MySQL 8.0 支持真正的降序索引定义
临界值LIMIT 很小时优化器可能主动选择排序,未必是坏事
覆盖索引索引包含查询所需列时可避免回表,效果更佳

典型使用场景

  • 列表页按时间倒序:用户维度的最新记录查询,是最常见的场景
  • 分页查询:深翻页时排序开销被放大,索引收益更明显
  • 排行榜与统计:按分数或金额排序并取前若干条
  • 范围条件加排序:范围列之后接排序列,只能部分利用索引
  • 升降序混排:先确认版本是否支持降序索引,再决定索引设计

正反例

正反例
-- 索引 (user_id, created_at) 的情况下
-- 用得上:条件列是索引前导,排序列顺延
EXPLAIN SELECT * FROM appdb.orders WHERE user_id = 1 ORDER BY created_at DESC LIMIT 20;
-- 用不上:条件列不在索引里,仍需额外排序
EXPLAIN SELECT * FROM appdb.orders WHERE status = 1 ORDER BY created_at LIMIT 20;
-- 按需建立覆盖索引(把查询列补进索引)
ALTER TABLE appdb.orders ADD INDEX idx_user_created_status (user_id, created_at, status);
-- 确认排序是否消除:看执行计划的附加信息
EXPLAIN FORMAT=JSON SELECT * FROM appdb.orders WHERE user_id = 1 ORDER BY created_at DESC LIMIT 20;

执行计划判读

信息含义处理建议
出现排序标记需要额外排序步骤调整索引顺序或改写条件列
键列为空未使用索引检查条件列是否可被索引覆盖
扫描行数巨大全表或大范围扫描补前导列索引或收窄条件
附加信息含临时表结果集被物化减少分组与排序组合带来的物化
小 LIMIT 的语句有时额外排序反而更快:优化器会权衡,不要看到排序标记就断定有问题,先看实际耗时。
只给排序列建索引而条件列不在索引中:扫描量会比预期大得多,索引形同虚设。

易错点

  • 只给排序列建索引:条件列没进索引,扫描行数爆炸
  • 假设反向扫描一定可用:低版本或特定索引结构下并不成立,必须实测
  • 索引用不到的列上加函数或隐式类型转换:索引直接失效
  • 忽略分页深度:深翻页时即使有索引,偏移量很大也会退化为大量扫描

数据来源与参考

  • MySQL 8.0 Reference Manual: ORDER BY Optimization

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌