MySQL 8.0+ 排序行为变更导致“内存不足”警告:默认启用 packed addons 模式,整行(含大字段)加载进 sort_buffer_size;应优先字段裁剪、建覆盖索引或用 max_length_for_sort_data 降级排序模式。

这个警告不是内存真不够,而是 MySQL 8.0+ 的排序行为变了——它正试图把整行(含 JSON/TEXT/BLOB)塞进 sort_buffer_size,而不是像以前那样只排主键和排序字段。
为什么升级后突然报这个警告?
MySQL 8.0.20 起,默认启用“packed addons”排序模式:只要 SELECT 里带大字段(比如 log_json),哪怕只 LIMIT 10,也会把整条记录加载进排序缓冲区。默认 sort_buffer_size = 262144(256KB),一条 2MB 的 JSON 就直接溢出。
- 不是缓冲区变小了,是它被要求干更重的活
-
EXPLAIN FORMAT=JSON里看"using_filesort": true+"sort_mode": "sort_key, additional_fields",就说明正在走全字段排序 - 查
SHOW GLOBAL STATUS LIKE 'Sort_merge_passes',如果每分钟涨几十次,才是真瓶颈;如果几乎不涨,纯属“误报”
别急着 SET GLOBAL sort_buffer_size
这个参数是 per-connection 独占分配的,设成 4MB,100 个并发就固定吃掉 400MB 内存,且 MySQL 8.0.22+ 已禁止 SET GLOBAL 动态修改,必须重启生效。
- 改了配置文件但
SELECT @@sort_buffer_size还是 262144?——旧连接不继承新值,得重连或手动SET SESSION sort_buffer_size = 4194304 - ORM(如 Django)或中间件(如 ShardingSphere)常在连接建立后重置会话变量,覆盖你的设置
- 更安全的做法是内联提示:
SELECT /*+ SET_VAR(sort_buffer_size = 4194304) */ id, create_time, log_json ...
真正有效的 3 种实操方案
优先级从高到低:
-
字段裁剪:先查 ID 和排序字段,再按需补大字段。例如:
SELECT id, create_time FROM audit_log WHERE status = 1 ORDER BY create_time DESC LIMIT 10,再用SELECT log_json FROM audit_log WHERE id IN (…) -
加覆盖索引:如果查询固定是
WHERE status = ? ORDER BY create_time,建INDEX idx_status_ctime (status, create_time),让排序完全走索引,绕过sort_buffer_size -
显式降级排序模式:用
max_length_for_sort_data强制回退 rowid 模式:SELECT /*+ SET_VAR(max_length_for_sort_data = 1024) */ id, create_time, log_json ...。比调sort_buffer_size更精准、副作用更小
容易被忽略的协同参数
sort_buffer_size 不是单打独斗的。它和 max_length_for_sort_data 共同决定是否启用 full-columns 排序:
- 默认
max_length_for_sort_data = 1024,单行参与排序的数据超限就自动切到 rowid 模式 - 如果你真要排大字段,得同步调大它(比如设为 4096),否则光调
sort_buffer_size没用 -
read_rnd_buffer_size管的是排序后回表取数据的缓冲,只有执行计划同时出现Using filesort和Using where时才可能影响它
复杂点在于:你看到的“内存不足”,大概率是 SQL 写法暴露了历史隐患,而不是配置参数本身的问题。最稳妥的路径是先裁字段、再建索引、最后才考虑临时调参——而且永远只对具体查询生效,不碰全局配置。


















