直接结论:这不是内存总量不够,而是单次排序操作把sort_buffer_size塞爆了——90%的case根源在索引缺失或大字段参与排序,不是参数该调多大;需先确认错误日志含“out of sort memory”,再用explain验证using filesort,精简json/blob字段或补降序复合索引才是根本解法。

直接结论:这不是内存总量不够,而是单次排序操作把 sort_buffer_size 塞爆了——90% 的 case 根源在索引缺失或大字段参与排序,不是参数该调多大。
先确认是不是真由排序内存触发的错误
别一看到 Out of sort memory 就改配置。打开 MySQL 错误日志(通常是 /var/log/mysql/error.log 或 mysqld.err),确认报错完整信息里是否含 Out of sort memory,而不是 Query execution was interrupted 或 Server shutdown in progress ——后者说明是超时、被 KILL 或 mysqld 进程被 OOM Killer 杀掉,得去系统日志查 dmesg -T | grep -i "killed process"。
同时看应用层日志:Spring 项目若抛出 java.sql.SQLException: Out of sort memory,才真正对应这个瓶颈;如果只是事务回滚但没这句异常,大概率是进程被杀,不是排序问题。
用 EXPLAIN 看清是否走了 filesort
EXPLAIN 比调参快十倍。对报错 SQL 执行:
EXPLAIN SELECT id, created_at FROM t WHERE ... ORDER BY created_at DESC;
重点盯 Extra 列:
- 出现
Using filesort→ 没走索引排序,必须补索引 - 出现
Using temporary; Using filesort→ 同时触发临时表 + 文件排序,问题更重 - 即使
rows很小,只要Using filesort存在,sort_buffer_size就可能被 JSON 字段撑爆(MySQL 8.0.20+ 默认把 JSON 当作 packed addons 加载进排序缓冲)
注意:MySQL 8.0 支持降序索引,ORDER BY created_at DESC, id ASC 要建 INDEX(created_at DESC, id ASC) 才能完全覆盖;只建 (created_at, id) 可能仍触发 filesort。
检查 JSON/BLOB 字段是否在悄悄吃光缓冲区
MySQL 8.0.20 起,JSON 和 GEOMETRY 字段在排序时默认以 packed addons 方式加载进 sort_buffer_size。哪怕只取 10 行,单条 JSON 值几 MB 就会直接冲垮默认 256KB 缓冲。
验证方法很简单:
- 把原 SQL 的
SELECT *或含JSON/BLOB的字段精简掉,比如改成SELECT id, created_at FROM t WHERE ... ORDER BY created_at DESC LIMIT 10 - 如果精简后不报错,基本锁定是大字段导致的溢出
别急着把 sort_buffer_size 设成 4M ——它是每个连接独占的,100 个并发就吃掉 400MB,容易引发全局内存压力。
为什么不能只靠调大 sort_buffer_size
盲目增大 sort_buffer_size 是最危险的“解法”:
- 它掩盖了索引缺失、字段冗余等真实设计问题
-
sort_buffer_size是 per-connection 参数,设高后高并发下极易耗尽物理内存 - 对分页偏移量大的查询(如
LIMIT 10000, 20),即使加了索引,MySQL 仍可能先排序全部匹配行再截断,大字段照样进缓冲 - MySQL 9.6.0(2026 年新版本)虽优化了 Binlog 一致性,但排序内存机制未变,老问题照旧
真正难处理的是那种“数据量不大、但字段极大”的组合,比如审计日志表里存了几 MB 的 report_data JSON 字段,又要做 ORDER BY create_time ——这种场景下,字段拆表或异步加载才是长期解法,不是调一个参数就能绕过去的。











