Using filesort表示排序未走索引,需额外排序;关键在WHERE条件匹配索引最左前缀、ORDER BY字段紧接其后且方向一致、查询字段尽量覆盖索引。

EXPLAIN 出现 Using filesort 就说明排序没走索引
只要 EXPLAIN 输出的 Extra 列里有 Using filesort,就代表 MySQL 没法直接按索引顺序读出结果,必须额外做一次内存或磁盘排序——这不是“可能慢”,是数据量一上万就明显卡顿的硬伤。
常见误判是以为“只要 ORDER BY 字段上有索引就行”。其实关键在三点是否同时满足:
-
WHERE条件字段必须是索引最左连续部分(比如索引是(shop_id, status, created_at),WHERE shop_id = 1001 AND status = 'paid'可以;但WHERE status = 'paid'就不行) -
ORDER BY字段必须紧接在WHERE字段之后,且方向一致(如索引定义为(shop_id ASC, status ASC, created_at DESC),那ORDER BY shop_id, status, created_at DESC才能用上;混用ASC/DESC在 MySQL 5.7 及以前基本无效) - 如果查询含
SELECT *或非索引字段,即使排序走了索引,回表成本高,优化器也可能主动放弃索引,转而全表扫描 + filesort
建复合索引时,字段顺序和方向必须严格匹配查询模式
索引不是“包含字段就行”,而是结构必须和实际查询对得上。比如想执行:
SELECT order_no, amount 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, order_no, amount),变成覆盖索引
反例:INDEX (created_at, shop_id) 对上面查询完全无效;INDEX (shop_id, created_at) 在 MySQL 5.7 下无法支持 DESC 排序;INDEX (status, shop_id, created_at) 因为 WHERE 中 shop_id 是等值、status 是等值,但顺序和索引不一致,大概率被忽略。
GROUP BY 和 ORDER BY 共存时,别指望一个索引通吃
当语句里同时出现 GROUP BY a ORDER BY b,尤其 b 不是分组字段或计算值时,MySQL 通常无法复用同一个索引完成两件事。
-
GROUP BY a ORDER BY a:索引(a)足够 -
GROUP BY a ORDER BY created_at:即使有(a, created_at)索引,优化器也可能因分组聚合逻辑优先,放弃用它做最终输出排序 -
SELECT a, COUNT(*) FROM t GROUP BY a ORDER BY COUNT(*) DESC:COUNT(*)是运行时计算值,不可能被索引覆盖,必然Using filesort——这时候硬加索引没用,该物化中间结果就物化,该应用层排序就应用层排序
更危险的是 SELECT a, b FROM t GROUP BY a 这种写法:在严格 SQL mode 下直接报错;在宽松模式下结果不可控,还让索引失效。
覆盖索引不是锦上添花,而是避免 filesort 的必要条件之一
很多同学建了 (status, created_at) 索引,EXPLAIN 却还是显示 Using filesort,原因往往是 SELECT * 或查了不在索引里的字段。
例如:
SELECT id, name, email FROM users WHERE status = 'active' ORDER BY created_at;
即使有 INDEX (status, created_at),优化器发现要回表取 id/name/email,代价太高,宁可全表扫 + filesort。真正有效的做法是:
- 把返回字段全加进索引末尾:
INDEX (status, created_at, id, name, email) - 确保
ORDER BY字段是索引最右连续部分(这里就是created_at) - 确认
WHERE条件只用了索引最左前缀(status = ...符合)
这时候 EXPLAIN 的 Extra 会变成 Using index,表示排序和取值都在索引里完成——这才是覆盖索引生效的标志。最容易被忽略的一点:索引字段顺序一旦定死,就很难靠改 SQL 绕过去;建错顺序,等于白建。


















