join + order by 易慢,因结果集无法用索引排序时触发using filesort;需索引按「关联字段→排序字段」顺序组织,如left join orders on u.id = o.user_id order by o.created_at desc,应建index(user_id, created_at),否则全量排序导致性能断崖。

能直接用索引完成排序的 JOIN,比先关联再排序快得多;前提是索引字段顺序、查询条件和 ORDER BY 字段严格匹配。
为什么 JOIN + ORDER BY 容易慢
MySQL 在执行 JOIN 后如果发现结果集无法利用现有索引排序,就会触发 Using filesort —— 这意味着它要把中间结果全拉出来,在内存或磁盘上额外做一次排序。数据量一大,性能断崖式下跌。
常见错误现象包括:
- EXPLAIN 输出中
Extra列出现Using filesort - 多表关联后加
ORDER BY t2.created_at DESC,但t2上只有单列created_at索引,没覆盖关联字段 - LEFT JOIN 右表字段参与排序,但右表索引没包含 JOIN 条件字段
复合索引字段顺序必须匹配 JOIN + ORDER BY 路径
关键不是“有没有索引”,而是索引字段是否按「关联条件 → 排序字段」的顺序组织。InnoDB 的 B+Tree 天然支持范围扫描+有序返回,但只对索引最左前缀生效。
假设你有:
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.status = 'active' ORDER BY o.created_at DESC;
这时要让排序走索引,orders 表上的索引不能只建 (created_at),而应建:
ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
理由是:
-
user_id是 JOIN 条件字段,必须放最左——否则索引无法被用于定位匹配行 -
created_at紧跟其后,才能在满足user_id = ?的前提下,直接按该字段倒序取出结果,避免 filesort - 如果还带
WHERE o.status = 'paid',就得把status放中间:(user_id, status, created_at),且 WHERE 中必须等值过滤status,否则created_at索引部分失效
LEFT JOIN 和 INNER JOIN 对索引要求不同
INNER JOIN 两边都可能驱动排序,但 LEFT JOIN 的排序字段若来自右表(orders),则右表索引必须覆盖「关联键 + 排序键」;左表(users)即使有 status 等过滤条件,也只影响驱动表选择,不直接影响排序效率。
容易踩的坑:
- 给
orders建了(created_at, user_id)—— 字段顺序反了,user_id不在最左,JOIN 时无法使用该索引定位行 - 用了
OR或!=过滤右表字段,导致整个右表索引失效,连带排序退化 -
ORDER BY o.created_at DESC, o.id ASC,但索引是(user_id, created_at)—— 缺少id,仍会触发 filesort
覆盖索引能进一步消除回表,但别为了覆盖硬凑字段
如果 SELECT 只要 o.amount 和 o.created_at,那索引 (user_id, created_at, amount) 就能让排序+取值全在索引页完成,不用回主键聚簇索引查整行。
但要注意:
- 字段越多,索引体积越大,写入开销越高,尤其
amount是非固定长度字段时更明显 - 如果业务中
ORDER BY经常切换方向(有时ASC,有时DESC),MySQL 8.0+ 才支持在单个索引里混合定义方向,如(user_id, created_at DESC);5.7 及以前只能统一升序,降序排序仍可能无法利用索引 - 字符串字段参与排序时,务必确认字符集和排序规则一致,否则隐式转换会让索引失效
真正难的不是建索引,而是把 JOIN 路径、WHERE 条件、ORDER BY 字段、SELECT 字段这四者对齐到同一个索引的最左前缀上——漏掉任意一环,filesort 就会悄悄回来。











