应查optimizer_trace中filesort_summary的disk_used是否非零,以及sort_merge_passes是否持续上涨;前者为已落盘的直接证据,后者是缓冲区不足的铁证。

怎么看当前SQL有没有被sort_buffer_size卡住
只查SHOW VARIABLES LIKE 'sort_buffer_size'没用——它只告诉你“理论上能分多少”,不反映实际用了多少、够不够。真正要确认是否受限,得看执行时的内存和磁盘行为。
先跑一次EXPLAIN FORMAT=JSON,重点找"using_filesort": true;再开optimizer_trace抓真实消耗:
SET optimizer_trace="enabled=on";- 执行你的
SELECT ... ORDER BY ... SELECT * FROM information_schema.OPTIMIZER_TRACE\G
在输出里搜filesort_summary,看两个关键字段:
-
memory_used:实际用了多少字节内存排序 -
disk_used:非零就说明已落盘——缓冲区确实不够,不是“可能不够”,是已经溢出了
为什么Sort_merge_passes比Using filesort更值得盯
EXPLAIN里出现Using filesort只代表走了文件排序路径,不等于性能差;而Sort_merge_passes持续上涨,才是缓冲区真扛不住的铁证。
执行SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';,观察单位时间内的增量:
- 每分钟涨几十上百 → 很大概率是单次排序数据量大 +
sort_buffer_size太小,正在反复切块、写盘、归并 - 如果
Sort_scan和Sort_range也同步飙升 → 更可能是索引缺失,排序本身无法避免,调buffer只是治标 - 若
EXPLAIN FORMAT=JSON里using_filesort节点还带"using_temporary": true→ 不仅排序落盘,连中间结果都撑爆内存了
怎么确认是buffer小,还是单行太大
sort_buffer_size再大,也救不了“单行参与排序的数据超限”这个问题。MySQL默认用max_sort_length = 1024截断长字段,比如你ORDER BY CONCAT(name, address, phone),但其中某字段含大文本,实际进缓冲区的只有前1024字节,排出来的顺序可能错,调buffer完全无效。
查当前设置:SHOW VARIABLES LIKE 'max_sort_length';
临时压测可试:SET SESSION max_sort_length = 4096;,配合sort_buffer_size一起调,但注意:
- 它不会自动扩展内存,只是放宽截断阈值
- 如果排序字段本身是
TEXT或JSON,且业务上真需要全量比较,就得考虑函数索引或冗余排序列
会话级设置为什么经常失效
很多ORM(如Django)或分库中间件(如ShardingSphere)会在连接建立后主动重置会话变量,导致你SET SESSION sort_buffer_size = 4194304;之后,下一条SQL执行时又回到默认值。
安全做法是把设置内联到SQL里:
SELECT /*+ SET_VAR(sort_buffer_size = 8388608) */ * FROM logs ORDER BY created_at DESC LIMIT 1000;
这样能确保该语句一定用上指定大小,不受连接复用或中间件干扰。但要注意:
-
SET_VAR语法仅MySQL 8.0.12+支持 - 单位必须是字节,不能写
8M或8096K——MySQL不识别后缀,会静默失败 - OLTP主库别长期设高,报表类查询才适合临时拉高,用完即释放
实际调优中最容易忽略的,是把sort_buffer_size当成“越大越快”的开关。它本质是个线程独占缓冲区,不共享、不复用,盲目拉高只会让并发一上来就吃光内存,而真正的瓶颈往往藏在索引设计或max_sort_length这类隐性限制里。











