ORDER BY慢主因是未走索引排序而触发文件排序;应建匹配WHERE+ORDER BY的复合索引,遵循最左前缀原则,优先使用覆盖索引、避免函数干扰,并用游标分页替代深分页。

ORDER BY慢,绝大多数时候不是SQL写错了,而是数据库没走索引排序,被迫做文件排序(Using filesort)。真正高效的排序,常常是“没发生排序”——靠索引天然有序直接读取。
让排序走索引:复合索引要配得准
索引不是给排序字段单独建一个就行,必须匹配查询整体结构:
- WHERE条件字段 + ORDER BY字段,按顺序组成复合索引,且满足最左前缀原则。例如:
WHERE status = 'shipped' ORDER BY created_at DESC,应建索引(status, created_at DESC) - MySQL 8.0+ 支持混合方向(如
a ASC, b DESC),但5.7及更早版本要求所有字段方向一致,否则无法复用 - 如果还查
id, title等字段,把它们加进索引末尾,构成覆盖索引,避免回表,如(status, created_at DESC, id, title) - 索引列顺序不能颠倒:有索引
(a, b),ORDER BY b, a就无法利用
别让函数毁掉索引
一旦在ORDER BY里用函数或表达式,索引基本失效:
- 写成
ORDER BY UPPER(name)→ 改为建函数索引(MySQL 8.0+/PostgreSQL):CREATE INDEX idx_name_upper ON users (UPPER(name)) - 写成
ORDER BY DATE(created_at)→ 改为范围条件过滤:WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01',再按原字段排序 - 避免
ORDER BY RAND()、ORDER BY ABS(x)这类必然全表扫描的操作
分页越深越卡?换游标分页
LIMIT 100000, 20 不是慢在排序,而是数据库仍得从头扫出100020行再丢掉前100000行:
- 改用游标分页:记录上一页最后一条的排序键值(如
created_at和id),下一页查WHERE created_at < ? OR (created_at = ? AND id < ?) ORDER BY created_at DESC, id DESC LIMIT 20 - 确保排序字段组合具备唯一性,否则可能漏数据或重复;推荐用时间戳+主键联合判断
- 首屏仍可用
ORDER BY ... LIMIT 20,后续全部走游标
减少参与排序的数据量
排序开销和待排行数非线性增长,越早缩小结果集越有效:
- WHERE条件务必有高效索引,避免先 JOIN 几百万行再排序
- 多表关联时,优先让排序发生在小表或驱动表上;大表字段尽量延迟关联——先用子查询取ID排序,再JOIN补详情
- 对时间类字段,结合分区表(如按月分区),让优化器自动裁剪无关分区
- 检查执行计划:EXPLAIN 中 Extra 列出现 Using filesort 就说明没走索引排序;出现 Using temporary 往往伴随 JOIN 或 GROUP BY,也需警惕











