mysql order by 要走索引排序,必须满足:①字段是联合索引的连续最左前缀;②方向一致(8.0前不支持混合asc/desc);③where范围查询后order by列失效;④需兼顾过滤与排序建联合索引。

ORDER BY 字段必须是索引的最左前缀
MySQL 只有在 ORDER BY 子句匹配索引的**连续最左列**时,才能避免文件排序(Using filesort)。比如有联合索引 INDEX (a, b, c),以下写法能走索引:
ORDER BY aORDER BY a, bORDER BY a, b, c
但 ORDER BY b、ORDER BY a, c(跳过 b)、ORDER BY b, c 都无法利用该索引做排序。注意:即使 a 有 = 条件,ORDER BY b, c 仍大概率触发 Using filesort —— 因为索引中 a 值相等的记录,b, c 部分才是有序的,而优化器通常不认为这足够“全局有序”。
避免混合 ASC/DESC 导致索引失效
MySQL 8.0 之前,如果联合索引定义为 INDEX (a ASC, b ASC),而查询写成 ORDER BY a ASC, b DESC,就无法使用该索引排序(会回退到 Using filesort)。MySQL 8.0+ 支持降序索引,可显式建为 INDEX (a ASC, b DESC),此时 ORDER BY a ASC, b DESC 才能命中。
- 检查执行计划:出现
Using filesort就说明没走索引排序 - 旧版本想兼容混合方向,只能建两个索引(如
(a,b)和(a,b DESC)),但后者在 5.7 及以前语法不合法 -
DESC在索引定义中不是注释,是真实存储方向,影响 B+ 树遍历顺序 - 应建联合索引:
INDEX (status, created_at)(注意顺序:先 where 列,再 order by 列) - 如果还有
SELECT *或需要返回其他字段,考虑是否加覆盖索引,避免回表(例如INDEX (status, created_at, id, name)) - 当
WHERE条件用的是范围(如status > 0),则ORDER BY后续列**无法使用索引排序**(B+ 树中范围扫描后的数据不再保证有序) - 改用游标分页:
WHERE created_at < '2024-01-01' ORDER BY created_at DESC LIMIT 20 - 如果必须跳转,考虑用延迟关联(
SELECT * FROM t JOIN (SELECT id FROM t ORDER BY x LIMIT 10000,20) AS tmp USING(id))减少排序行数 -
SQL_BUFFER_RESULT有时能缓解,但治标不治本
WHERE + ORDER BY 组合时,索引要覆盖过滤和排序需求
典型陷阱:给 WHERE status = 1 建了 INDEX (status),又想 ORDER BY created_at,结果还是 Using filesort —— 因为单列索引 status 不含 created_at,无法排序。
小结果集别强求索引排序,大分页慎用 LIMIT offset
对 ORDER BY id LIMIT 10 这类小结果,即使没走索引排序,成本也极低;但 ORDER BY created_at LIMIT 10000, 20 会让 MySQL 先排序前 10020 行,再丢弃前 10000 行 —— 即使 created_at 有索引,I/O 和 CPU 开销依然巨大。
真正难优化的,往往不是“有没有索引”,而是“索引列顺序是否贴合查询模式”以及“范围条件之后还能不能排序”。这两个点漏掉一个,EXPLAIN 里就稳稳写着 Using filesort。










