应盯sort_merge_passes是否持续上涨(每分钟几十上百才是铁证),而非仅看explain的using filesort;若sort_scan/sort_range低而sort_merge_passes高,说明缓冲区小;若三者均高,则优先建索引;同时检查using_temporary和max_length_for_sort_data限制。

先盯 Sort_merge_passes,不是看 EXPLAIN 有没有 Using filesort —— 后者只说明走了文件排序,不说明缓冲区小;前者持续上涨才是磁盘归并频繁的铁证。
怎么确认真是 sort_buffer_size 不够,而不是索引没建对
只查 EXPLAIN 输出容易误判。真正要对比的是三个运行时状态变量:
-
Sort_scan:全表扫描后排序(无索引或索引未被用) -
Sort_range:索引范围扫描后排序(索引部分生效) -
Sort_merge_passes:归并次数(每分钟涨几十上百才危险)
如果 Sort_scan 和 Sort_range 都很低,但 Sort_merge_passes 每分钟持续上涨,说明单次排序数据量大 + 缓冲区小;如果三者都高,大概率是查询根本没走索引,该加索引而非调 buffer。
再查 EXPLAIN FORMAT=JSON,看 using_filesort 节点里是否同时出现 using_temporary —— 有则说明中间结果也撑爆内存了,sort_buffer_size 单独调大意义不大。
为什么调大 sort_buffer_size 后还是慢甚至报 Sort aborted
常见错觉:以为 buffer 够大,排序就一定进内存。实际上有两个关键限制常被忽略:
-
max_length_for_sort_data默认为 1024,单行参与排序的数据超 1KB 就强制切到 rowid 模式(只存排序字段+主键),实际进 buffer 的数据量很小。典型诱因是SELECT *返回宽表,但ORDER BY只用两个INT字段,却因某TEXT字段拉高单行体积 -
sort_buffer_size是每个连接独占分配,设成 4MB 后,100 个并发连接就固定吃掉 400MB 内存,还没算其他 buffer。MySQL 8.0.22+ 已禁止SET GLOBAL动态修改,改了配置文件也得重启才生效
可试:SET SESSION max_length_for_sort_data = 4096;,配合 sort_buffer_size 一起压测。注意:它只对当前会话生效,且调高会增加内存占用。
怎么安全地调 sort_buffer_size 并验证效果
这个参数不是全局池子,是 per-connection 独占的,设错单位、范围或生效层级基本等于白调:
- 单位是字节:
SET SESSION sort_buffer_size = 4194304;是 4MB,不是4M或4096K(MySQL 不识别后缀) - 全局配置(
my.cnf)改完必须重启 MySQL 才生效;会话级设置只影响当前连接,适合报表类 SQL 临时加大 - ORM 或中间件(如 Django、ShardingSphere)常在连接建立后重置会话变量,得在 SQL 前显式加
SELECT /*+ SET_VAR(sort_buffer_size = 4194304) */ ... - 线上 OLTP 主库建议保持默认 256KB~1MB;专用从库跑报表时,可会话级设到 4MB~8MB,跑完即释放
验证是否有效,不能只看单次查询变快,必须持续监控:SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';,观察其增长速率是否下降。如果没降,优先检查索引覆盖、字段类型或 max_sort_length 设置。
最常被忽略的一点:sort_buffer_size 是“救火参数”,不是“优化开关”。真正卡顿或爆内存时,90% 的情况不是 buffer 太小,而是索引没建对——比如 ORDER BY status, created_at 却只在 status 上建了单列索引。











