应先确认explain是否出现using filesort,再查sort_merge_passes是否持续上涨;若为0或极低,则问题多因索引缺失、单行过大(如json字段)或磁盘写入失败,而非buffer不足。

直接调大 sort_buffer_size 很可能让问题更糟——它不是共享池,而是每个连接独占的内存,100个并发查报表,设成 8MB 就吃掉 800MB,OOM 风险远高于解决排序慢。
怎么确认真是 sort_buffer_size 不够,而不是索引没建对
90% 的 “Sort aborted” 错误根本不需要调参,只是查询没走索引排序。关键看两处:
-
EXPLAIN输出的Extra列是否含Using filesort—— 出现就说明 MySQL 放弃索引,转而全量加载数据进 buffer 排序 - 执行
SHOW STATUS LIKE 'Sort_merge_passes';,数值持续上涨(尤其配合高Sort_scan或Sort_range)才代表真落盘;如果为 0 或极低,那错误大概率是临时文件写不进磁盘,或单行太大(比如 JSON 字段超 4MB)直接撑爆 buffer
为什么改了 sort_buffer_size 还报错
常见踩坑点:
-
sort_buffer_size是 per-thread 参数,SET GLOBAL修改只对新连接生效,老连接仍用旧值;线上应优先用SET SESSION sort_buffer_size = 4194304;临时加给当前报表 SQL - Linux 下该值超过 2MB 可能触发
mmap()分配,反而降低效率;官方文档明确提示:256KB–2MB 是较安全区间 - MySQL 8.0.20+ 对 JSON/TEXT 字段默认启用“单次加载”排序模式,哪怕只
SELECT id, created_at,只要表里有未被覆盖的 JSON 列,也可能把整字段内容拉进 buffer —— 查AVG(LENGTH(json_col))确认是否超 buffer 容量
比调参更有效的三类解法
真正稳的优化路径是:先让排序消失,再让排序变小,最后才考虑调 buffer。
- 建联合索引匹配
WHERE + ORDER BY顺序:例如WHERE status = 1 ORDER BY created_at DESC,必须建INDEX(status, created_at);反过来或加函数(ORDER BY DATE(created_at))都无效 - 用覆盖索引避免回表:把
SELECT所有字段都塞进索引,比如INDEX(status, created_at, user_id, amount),这样排序在索引 B+ 树内完成,不触发filesort - 限制排序规模:业务真需要查全量 TOP 10000 吗?加
LIMIT 100能让 buffer 占用直降 99%;深度分页改用游标(WHERE id > ? ORDER BY id LIMIT 50),绕过 OFFSET 全量重排
什么情况下才该调 sort_buffer_size
仅当满足全部条件时才考虑:
- 已确认走了索引排序(
EXPLAIN无Using filesort),但Sort_merge_passes > 0且稳定上升 - 单行排序数据很轻(无 JSON/TEXT/BLOB),估算总大小 ≈ 行数 ×(排序字段长度 + 主键长度)< 4MB
- 该实例是专用报表从库,
max_connections≤ 50,物理内存 ≥ 32GB,可会话级设到 4MB~6MB - 绝对不在 my.cnf 里全局设超过 4MB,尤其 OLTP 主库保持默认 256KB~1MB
最常被忽略的一点:sort_buffer_size 和 read_rnd_buffer_size 常协同工作,后者控制排序后回表读取的批量大小,两者同时设大反而加剧竞争——调一个,另一个得同步评估。











