应查Sort_merge_passes是否持续上涨,而非仅看EXPLAIN的Using filesort;若该值每分钟涨几十上百,说明MySQL正反复切分、写盘、归并,才是sort_buffer_size不足的铁证,需结合Sort_scan/Sort_range及using_temporary判断是否索引缺失或rowid模式导致。

怎么确认真是 sort_buffer_size 不够,而不是索引没建对
只看 EXPLAIN 里有没有 Using filesort 完全不够——它只说明走了文件排序,不说明是不是缓冲区小。真正要盯的是运行时状态:Sort_merge_passes 是否持续上涨。
执行 SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';,如果该值每分钟涨几十甚至上百,说明 MySQL 正在反复切分、写磁盘、再归并,这才是缓冲区不足的强信号。
- 对比
Sort_scan和Sort_range:若两者低但Sort_merge_passes高,大概率是单次排序数据量大 + 缓冲区小 - 若三者都高,更可能是查询根本没走索引,得先加索引
- 用
EXPLAIN FORMAT=JSON查using_filesort节点里是否同时出现using_temporary,有则说明中间结果也撑爆内存了
为什么调大 sort_buffer_size 后还是慢
常见错觉:以为只要 buffer 够大,排序就一定进内存。实际上,sort_buffer_size 再大,也可能卡在 max_length_for_sort_data 的默认限制上。
MySQL 默认 max_length_for_sort_data = 1024,意思是单行参与排序的数据超 1KB 就强制切到 rowid 模式(只存排序字段+主键),实际进 buffer 的数据量很小,调再大也没用。
- 典型诱因:你
SELECT *返回宽表,但ORDER BY只用两个INT字段,却因某TEXT字段拉高单行体积 - 可试
SET SESSION max_length_for_sort_data = 4096;,配合sort_buffer_size一起压测 - 注意:
max_length_for_sort_data调高会增加内存占用,且只对当前会话生效
怎么安全地调 sort_buffer_size
sort_buffer_size 是每个连接独占一份,不是全局池子。设错单位、范围或生效层级,基本等于白调。
- 单位是字节:
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,跑完即释放
比调参更关键的三个动作
单纯增大 sort_buffer_size 是治标。真正高效的做法是减少甚至消除排序开销:
- 为
ORDER BY字段建联合索引,且把WHERE条件字段前置,例如WHERE status=1 ORDER BY create_time DESC→ 建INDEX(status, create_time) - 避免在排序字段上用函数或表达式,如
ORDER BY UPPER(name)无法利用索引 - 用覆盖索引减少回表:把
SELECT中所有字段都包含进索引,避免排序后还要逐行回聚簇索引取数
最易被忽略的是:大字段(如 JSON、TEXT)哪怕只查 10 行,也可能因单行体积过大直接撑爆 buffer —— 这时候删字段比调参数快十倍。


















