using filesort 表示排序未走索引,mysql需额外排序;必须用联合索引覆盖where+order by字段、顺序和方向,且避免函数/表达式、字符集不一致、select *及深分页等问题。

EXPLAIN 出现 Using filesort 就说明排序没走索引
这不是警告,是确诊结果。MySQL 一旦在 EXPLAIN 的 Extra 列看到 Using filesort,代表它必须把满足 WHERE 条件的行全部捞出来,再在内存(sort_buffer_size)或磁盘临时文件里重排——数据量从 1 万涨到 10 万,耗时可能翻 5 倍以上。
常见误判是:“我给 created_at 加了单列索引,为什么还 Using filesort?” 因为单列索引只对 ORDER BY created_at 有效;一旦加了 WHERE user_id = ?,就必须用联合索引覆盖两者,否则优化器无法同时满足过滤和有序扫描。
- 检查
key列:如果是NULL,说明连索引都没命中,先解决 WHERE 条件索引问题 - 检查
type:如果是ALL或index但Extra有Using filesort,说明索引存在,但排序路径没被复用 - 注意 MySQL 版本差异:5.7 不支持混合 ASC/DESC 索引字段,建了
(a ASC, b DESC)也当全 ASC 处理;8.0+ 才真正生效
建联合索引必须严格匹配 WHERE + ORDER BY 顺序和方向
索引不是“有就行”,而是要像插钥匙一样严丝合缝。例如查询:
SELECT id, name FROM users WHERE status = 1 AND type = 'vip' ORDER BY created_at DESC;
对应索引必须是:
ALTER TABLE users ADD INDEX idx_status_type_created (status, type, created_at DESC);
不能写成 (created_at, status),也不能漏掉 type(哪怕它是等值条件),更不能把 created_at 写成 ASC——只要方向不一致,低版本 MySQL 就放弃索引排序。
- 最左前缀必须完整:WHERE 中用了
status = ? AND type > ?,那type后面的字段就无法用于排序 - 函数或表达式直接废掉索引:写
ORDER BY DATE(created_at)或UPPER(name),哪怕字段本身有索引,也必然触发Using filesort - 字符集/校对规则不一致也会隐式失效:比如表用
utf8mb4_0900_as_cs,但参数传的是utf8mb4_general_ci,可能导致索引无法匹配
SELECT * 是覆盖索引失效的高频原因
即使你建了完美匹配的联合索引,只要写 SELECT *,MySQL 就大概率放弃“索引扫描 + 直接返回”的路径,转而回表查聚簇索引——此时即使 Extra 没写 Using filesort,实际性能也不如预期,因为 I/O 次数暴增。
真正高效的写法是只选需要的字段,并确保它们全在索引里:
SELECT id, created_at, name FROM users WHERE status = 1 ORDER BY created_at DESC;
配合索引 INDEX(status, created_at DESC, name),就能走 Using index(Extra 显示该值),全程只读索引 B+ 树叶子节点,不回表、不排序。
- 如果业务真要所有字段,考虑冗余常用列进索引(空间换时间),但别把大字段(如
TEXT、JSON)加进去 - JOIN 场景下,只有驱动表的 ORDER BY 字段能靠索引优化;被驱动表(如
JOIN orders ON u.id = o.user_id ORDER BY o.created_at)基本无法利用其自身索引做排序 -
LIMIT要配合使用:没有LIMIT的ORDER BY可能强制全量排序;但LIMIT 10000, 20这种深分页,即使走索引也要跳过前 10000 行,建议改用游标分页(WHERE created_at )
绕不开 filesort 的场景只能调优或换方案
有些写法 MySQL 根本不给你走索引排序的机会,硬扛只会让 DBA 夜不能寐:
-
ORDER BY RAND():随机打乱天然是无序的,别试图索引优化,改用应用层抽样或预生成 ID 列表 -
ORDER BY JSON_EXTRACT(data, '$.score'):函数破坏索引有序性,应提前把关键字段冗余为普通列并建索引 -
ORDER BY a ASC, b DESC(MySQL 5.7):升序降序混用,老版本不认,要么升级,要么统一方向,要么加计算列 - 多表 JOIN 后对非驱动表字段排序:优化器通常拒绝用被驱动表索引,可考虑子查询物化或改写为
LATERAL(8.0.14+)
这些情况,与其死磕索引,不如接受 filesort 并调大 sort_buffer_size(注意是每个连接独占,别设太大导致 OOM),或把排序逻辑前置到应用层(如 Elasticsearch、Redis Sorted Set)。真正容易被忽略的,是那些看似“只是加了个函数”或“只多了一个字段”的小改动——它们足以让原本毫秒级的查询退化成秒级,且在测试环境几乎无法暴露。











