ORDER BY 为什么慢?关键在是否走索引
MySQL 的
ORDER BY不一定慢,慢是因为它常触发 filesort —— 即无法利用索引完成排序,转而把数据读进内存或临时文件再排序。只要
ORDER BY的字段和顺序能被某个索引“覆盖”,就大概率避免 filesort。
判断是否走索引,看
EXPLAIN输出的
Extra列:出现
Using filesort就是没走排序索引;若只有
Using index或为空,说明排序已下推到存储层。 复合索引顺序必须严格匹配
ORDER BY字段顺序(如索引是
(a,b,c),则
ORDER BY a,b可用,
ORDER BY b,c不可用) 升序/降序需一致:MySQL 8.0+ 支持混合方向索引(如
INDEX(a ASC, b DESC)),但 5.7 及以前只支持全 ASC 或全 DESC 索引用于排序 WHERE 条件字段如果也在同一索引中,且满足最左前缀原则,该索引更可能被复用为排序索引
哪些 ORDER BY 场景必然触发 filesort
以下写法几乎无法利用索引排序,应主动规避:
ORDER BY RAND():每次都要打乱全表,无索引可依
ORDER BY ABS(x)、
ORDER BY UPPER(name)等带函数的表达式:索引值 ≠ 计算后值,无法比对
ORDER BY a + b:同上,非确定性计算列无法走索引 跨表 JOIN 后
ORDER BY非驱动表字段:优化器通常放弃使用被驱动表的索引排序
SELECT *+
ORDER BY+
LIMIT大偏移量(如
LIMIT 100000, 20):即使走了索引,也要跳过前 10 万行,I/O 和 CPU 成本高
如何为 ORDER BY 建高效索引
建索引不是给排序字段单独加一个,而是结合查询整体结构设计。核心原则是:让索引同时覆盖
WHERE过滤 +
ORDER BY排序 +
SELECT返回字段(即“覆盖索引”)。
例如有查询:
SELECT id, name, created_at FROM users WHERE status = 1 ORDER BY created_at DESC LIMIT 20;
最优索引是:
ALTER TABLE users ADD INDEX idx_status_created (status, created_at DESC);把等值条件字段
status放最左(满足最左前缀) 紧跟排序字段
created_at,并显式声明
DESC(MySQL 8.0+ 支持;5.7 可省略,但实际仍按索引定义方向扫描) 若还频繁查
name,且不想回表,可扩展为
(status, created_at DESC, name)—— 但注意索引宽度增加,写入开销上升 避免在该索引中加入
id:主键自动包含在二级索引中,无需重复
ORDER BY 性能陷阱:LIMIT 配合偏移量的隐性成本
LIMIT m,n在 MySQL 中本质是“先取 m+n 行,再丢弃前 m 行”。当
m很大(比如分页到第 1000 页),即使走了索引,也要遍历大量索引节点。
替代方案不是换写法,而是换思路:
用游标分页(cursor-based pagination):记录上一页最后一条的created_at值,下一页查
WHERE created_at对高频分页场景,预生成分页映射表(如记录每页首条主键 ID),用主键查而非偏移量 禁止前端传任意大的
OFFSET,后端强制截断(如最大只允许
OFFSET 5000)
真正难优化的不是
ORDER BY本身,而是它和业务逻辑耦合后产生的“假需求”——比如要求用户能翻到第 10 万页的评论。这种场景,索引再好也救不了设计缺陷。
