Using filesort出现是因为MySQL无法利用索引有序性直接返回ORDER BY结果,即使加LIMIT也会触发额外排序;根本原因是WHERE与ORDER BY未共用同一联合索引、排序字段非最左前缀、方向不匹配、含函数或未覆盖SELECT字段。

ORDER BY + LIMIT 为什么还会触发 Using filesort
只要 EXPLAIN 的 Extra 列里出现 Using filesort,说明 MySQL 没法直接用索引顺序返回数据,得额外排序。哪怕加了 LIMIT,也不代表自动走索引——优化器会权衡:是走索引+回表 N 次快,还是全表扫描+内存排序快。当 OFFSET 很大(比如 LIMIT 10000, 20),或 SELECT * 导致回表开销高,优化器大概率放弃索引,直接全表扫+filesort。
-
WHERE和ORDER BY字段不在同一个联合索引里,即使各自有单列索引,也只用一个 -
ORDER BY字段用了函数,比如ORDER BY UPPER(name)或ORDER BY DATE(created_at),索引完全失效 - 索引方向不匹配:建的是
(a ASC, b ASC),但写ORDER BY a ASC, b DESC(MySQL 8.0 以下不支持混合方向) -
SELECT *强制回表,而索引没覆盖所有要查的字段,导致“能排序但不能取数”,优化器降级选择全表扫描
怎么建索引才能让 ORDER BY + LIMIT 走索引
核心是构造一个「覆盖索引」:把 WHERE 等值条件列、ORDER BY 列、SELECT 中需要的字段,按顺序塞进一个复合索引。顺序必须严格满足最左前缀原则。
- 等值条件(
=、IN)放最左,多个等值条件可任意顺序,但建议按区分度从高到低排 - 排序字段紧接其后,且方向(
ASC/DESC)必须和ORDER BY一致;MySQL 8.0+ 支持混合方向,但低版本必须全同向 - 把 SELECT 字段也加进来,形成覆盖,避免回表;例如
SELECT id, name, created_at→ 索引至少含(status, created_at, id, name) - 不要跳过前导列:索引是
(a, b, c),ORDER BY b, c无效;必须从a开始
为什么 LIMIT 大 OFFSET 依然慢,即使有索引
LIMIT 10000, 20 不是“跳到第 10000 行再取 20 条”,而是“读满 10020 行,丢掉前 10000 条,返回后 20 条”。InnoDB 必须逐行遍历索引,每跳过一行都算一次 I/O。哪怕索引很快,10000 次定位+判断也放大延迟。
- 实测中,
Innodb_rows_read增量 ≈OFFSET + LIMIT,证明 MySQL 真的读了那么多行 - 游标分页才是解法:记录上一页最后一条的
created_at和id,下一页写WHERE created_at < '2024-05-01' AND (created_at, id) < ('2024-05-01', 12345) ORDER BY created_at DESC, id DESC LIMIT 20 - 前端限制最大页码(如只允许到第 200 页),后端对超限请求直接拒绝,比硬扛更实际
sort_buffer_size 设多大才合适
这个参数控制单次排序可用内存上限。设太小(默认 256KB)会导致频繁磁盘归并,Sort_merge_passes 上升;设太大(比如 64MB)则可能挤占其他连接内存,引发整体抖动。
- 先查当前值:
SHOW VARIABLES LIKE 'sort_buffer_size'; - 观察排序压力:
SHOW GLOBAL STATUS LIKE 'Sort%';,重点关注Sort_merge_passes(归并次数)和Sort_scan(扫描排序次数) - 会话级调整更安全:
SET sort_buffer_size = 4194304;(4MB),仅影响当前查询,避免全局污染 - 真正有效的调优依赖实测:不同并发、不同数据量下,4MB 可能刚好,也可能不够——没有银弹值
最常被忽略的点是:索引是否生效,不取决于“有没有建”,而取决于查询写法是否严格匹配最左前缀、方向、无函数包裹。哪怕只多一个 UPPER() 或少一个等值条件,整个索引就作废。每次改完 SQL,务必 EXPLAIN 看一眼 Extra 和 key 字段。


















