using filesort 表示排序未走索引,是数据量过万即明显卡顿的硬伤;根本原因是索引结构与查询模式不匹配,如复合索引未满足最左前缀、order by 含函数、where 用范围条件或升降序不一致等。

EXPLAIN 出现 Using filesort 就代表排序没走索引,不是“可能慢”,是数据量一过万就明显卡顿的硬伤——必须让索引结构和查询模式严格对齐,否则加了索引也白加。
为什么加了索引还是出现 Using filesort
常见误判是“ORDER BY 字段有索引就行”。实际 MySQL 只能利用索引的最左前缀做排序,且要求字段顺序、升降序、WHERE 条件三者完全匹配:
-
WHERE中用了非最左字段(如索引是(a, b, c),却写WHERE b = 1),该索引无法用于排序 -
ORDER BY含函数或表达式(ORDER BY UPPER(name)、ORDER BY created_at + INTERVAL 1 DAY),索引值 ≠ 计算值,直接失效 - MySQL 5.7 及以前不支持混合 ASC/DESC:索引定义为
(user_id, created_at),但查询写ORDER BY user_id ASC, created_at DESC,仍触发Using filesort - WHERE 中存在范围条件(
created_at > '2023-01-01')后,索引中其后的字段无法参与排序
怎么建复合索引才真正覆盖 ORDER BY
核心是「过滤字段 + 排序字段」连续排列,中间不能断层。例如查询:
SELECT id, order_no FROM orders WHERE shop_id = 1001 AND status = 'paid' ORDER BY created_at DESC LIMIT 10;
最优索引是:
INDEX (shop_id, status, created_at DESC)
-
shop_id和status是等值条件,放最前;created_at是排序字段,紧接其后 -
created_at DESC必须显式声明(MySQL 8.0+ 才认;5.7 建了也无效,会按升序存) - 若还想避免回表,可扩展为覆盖索引:
INDEX (shop_id, status, created_at DESC, id, order_no) - 反例:
INDEX (created_at, shop_id)或INDEX (status, shop_id, created_at)都无法命中
多表 JOIN 后 ORDER BY 字段来自被驱动表怎么办
MySQL 默认只对驱动表(FROM 后第一张表)的 ORDER BY 字段尝试索引排序。一旦排序字段来自被驱动表(如 JOIN orders ON u.id = o.user_id ORDER BY o.total DESC),即使 orders 表上有 total 索引,也基本必出 Using filesort。
- 别指望在被驱动表上单独建索引解决这个问题
- 可行路径只有两条:一是改用延迟关联(Deferred Join),先用子查询拿到排序后的主键,再回表补字段;二是把被驱动表的关键排序字段“拉”进驱动表索引(仅当 JOIN 条件强关联、且能预估取值范围时可行)
- 典型延迟关联写法:
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id JOIN (SELECT user_id FROM orders WHERE user_id IN (SELECT id FROM users WHERE country = 'CN') ORDER BY total DESC LIMIT 10) tmp ON u.id = tmp.user_id;
EXPLAIN 里怎么看排序到底走没走索引
关键盯紧 Extra 列,不是看有没有索引,而是看它是否“下推”到了存储层:
-
Using index:排序由索引完成,最快路径 - 空(无
Extra):也表示排序走索引,只是没用到覆盖索引全部字段 -
Using filesort:明确警告:MySQL 正在回表取数据后二次排序 -
Using where; Using filesort:WHERE 走了索引,但排序没走——说明索引设计没兼顾排序需求 - 如果
rows值接近全表行数,又带Using filesort,基本等于全量数据在内存或磁盘上重排
最容易被忽略的是字段隐式转换:比如 user_id 是 INT,但查询里写成 WHERE user_id = '123',字符串与整数比较会触发类型转换,导致索引失效——这种问题不会报错,但 Using filesort 一定出现。











