mysql 8.0.20+排序内存溢出主因是json等大字段被整行加载进sort_buffer,非缓冲区不足;默认256kb易被单条几mb的json撑爆,盲目调大sort_buffer_size会引发高并发oom。

别急着改 sort_buffer_size —— MySQL 升级后排序抖动,大概率是 8.0.20+ 新排序行为暴露了旧 SQL 的隐患,不是缓冲区真不够。
为什么升级后突然报 Out of sort memory?
MySQL 8.0.20 起对大字段(JSON、TEXT、BLOB)的排序策略变了:不再默认走轻量的 rowid 模式(只排主键+排序字段),而是尝试把整行“打包”进排序缓冲区。哪怕你只 LIMIT 10,只要 SELECT 里带一个几 MB 的 report_data 字段,它就真往 sort_buffer_size 里塞几 MB 数据。
这在 5.7 或 8.0.19 及以前不会触发内存溢出,升级后却立刻崩 —— 不是缓冲区变小了,是它突然被要求干更多事。
常见错误 SQL 示例:
SELECT id, create_time, log_json FROM audit_log WHERE status = 1 ORDER BY create_time DESC LIMIT 10;
其中 log_json 单条平均 2MB,10 行就是 20MB,远超默认 256KB。
先确认是不是真要调 sort_buffer_size
盲目调参会掩盖问题,且高并发下极易引发内存雪崩。必须验证是否属于“可调”场景:
- 查状态:
SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';—— 如果该值几乎不涨(比如每分钟 - 看执行计划:
EXPLAIN FORMAT=JSON这条 SQL,重点找using_filesort节点下是否有"using_temporary": true—— 有则说明连中间结果都撑不住,已超出排序缓冲区范畴 - 测精简版:
SELECT id, create_time FROM audit_log WHERE status = 1 ORDER BY create_time DESC LIMIT 10;—— 如果不报错,基本锁定是宽表字段拖垮排序
比调参更有效的 3 种实操方案
真正治本的优化,优先级高于任何配置调整:
-
字段裁剪:把大字段移出主查询,用子查询或关联补 —— 尤其适用于分页列表页;例如先查 ID 和时间,再按需
SELECT log_json FROM audit_log WHERE id IN (…) -
加覆盖索引:如果
WHERE + ORDER BY组合固定(如WHERE status = ? ORDER BY create_time),建INDEX idx_status_ctime (status, create_time),让排序直接走索引,彻底绕过sort_buffer_size -
显式控制排序模式:临时禁用打包行为,强制回退到
rowid模式:SELECT /*+ SET_VAR(max_length_for_sort_data = 1024) */ id, create_time, log_json …—— 这比调sort_buffer_size更精准、副作用更小
如果真得调,怎么设才安全
仅当满足全部条件时才考虑会话级调整:Sort_merge_passes 持续上涨 + 排序字段确实无索引 + 单行体积小(
- 单位必须是字节:
SET SESSION sort_buffer_size = 4194304;(4MB),写成4M或4096K无效 - 避免全局改:
SET GLOBAL sort_buffer_size = ...需重启,且影响所有连接;线上 OLTP 主库建议保持默认 256KB~1MB - ORM 场景注意重置:Django、MyBatis 等常在连接初始化后执行
RESET SESSION,必须用/*+ SET_VAR(...) */hint 写在 SQL 开头 - 别忘了配对参数:
read_rnd_buffer_size在 rowid 模式下用于回表,若一起调大,需同步评估
最易被忽略的一点:MySQL 升级后的排序抖动,往往不是内存不够,而是旧 SQL 在新规则下“突然暴露了设计缺陷”。解决它,靠的是字段精简、索引补全和 hint 控制,而不是给每个连接多分几 MB 内存。











