MySQL的ORDER BY是否走索引取决于索引结构匹配度:需字段顺序、方向与索引定义完全一致,且不被WHERE打断;EXPLAIN显示Using filesort即未走索引。

ORDER BY 没走索引?先看执行计划和排序字段顺序
MySQL 的 ORDER BY 是否走索引,不取决于它写在 SQL 里,而取决于是否能复用已有的索引结构。最常见误区是以为「加了索引就自动加速排序」——其实只有当 ORDER BY 字段的顺序、方向(ASC/DESC)和索引定义完全匹配,且没有被 WHERE 条件打断时,才能避免 Using filesort。
用 EXPLAIN 查看 Extra 列:出现 Using filesort 就说明 MySQL 正在内存或磁盘做额外排序,性能风险高。
- 复合索引
INDEX(a, b, c)可以支持ORDER BY a, b或ORDER BY a, b, c(同向),但不能支持ORDER BY b, c(跳过前导列) -
ORDER BY a ASC, b DESC无法使用INDEX(a ASC, b ASC)(8.0+ 支持混合方向索引,但需显式定义INDEX(a ASC, b DESC)) - 如果
WHERE条件用了a = ? AND b > ?,那么ORDER BY c就无法复用该索引,必然触发 filesort
内存不足导致磁盘排序(Sort_merge_passes 飙升)
当 MySQL 无法在内存中完成排序时,会把数据分块排序再归并,这个过程叫 merge pass。Sort_merge_passes 状态值持续上升,说明 sort_buffer_size 设置过小,或者单次排序数据量太大。
不是越大越好:每个连接独占一份 sort_buffer_size,设成 256M 而并发 100,光排序缓冲就吃掉 25G 内存。
- 观察
SHOW GLOBAL STATUS LIKE 'Sort%',重点关注Sort_merge_passes和Sort_rows比值;比值 > 0.1 通常意味着需要调优 - 临时调整:连接内执行
SET sort_buffer_size = 4194304(4MB),比全局设置更安全 - 真正治本:减少
SELECT *,只查必要字段;避免在ORDER BY中用函数如ORDER BY UPPER(name)(强制逐行计算,无法走索引)
覆盖索引 + LIMIT 是最廉价的分页排序方案
带 LIMIT 的排序查询,尤其是分页(如 OFFSET 10000 LIMIT 20),最容易暴露排序性能问题。MySQL 仍要扫描前 10000 行才取后面 20 行,即使有索引。
解决思路不是“优化 OFFSET”,而是绕过它:
- 用游标分页:上一页最后一条记录的
id或create_time作为下一页查询条件,例如WHERE create_time - 确保
ORDER BY + LIMIT字段被覆盖索引包含,例如查询SELECT id, name FROM t ORDER BY create_time DESC LIMIT 20,可建索引INDEX(create_time DESC, id, name) - 避免
SELECT *配合大 OFFSET —— 即使有索引,回表读取全行也会放大 I/O 开销
临时表排序(Using temporary)比 filesort 更危险
当 ORDER BY 和 GROUP BY 不一致、或含 DISTINCT、或涉及多表 JOIN 且排序字段不在驱动表时,MySQL 可能先建临时表再排序,EXPLAIN 中显示 Using temporary; Using filesort。这时性能损耗是双重的。
典型场景:JOIN 后对非驱动表字段排序,比如 SELECT u.name FROM user u JOIN order o ON u.id = o.user_id ORDER BY o.pay_time,若 order 不是驱动表,几乎必走临时表。
- 检查
STRAIGHT_JOIN强制驱动表顺序,让排序字段所在表成为驱动表 - 把排序逻辑下推到子查询中:先在
order表按pay_time排序取 ID,再关联user表 -
tmp_table_size和max_heap_table_size必须同步调大,否则临时表会立刻落地磁盘,IO 成倍增加
排序性能问题往往藏在「看起来合理」的写法里:比如 WHERE 条件用了索引,但 ORDER BY 字段没包含在同一个索引中;或者 LIMIT 很小,却因 OFFSET 过大拖垮整个执行路径。真正的优化起点,永远是 EXPLAIN 和慢日志里的 Rows_examined 数字,而不是直觉。











