临时表溢出磁盘是聚合中间状态失控的信号,group by或count(distinct)将分组键与聚合值缓存在内存哈希表中,分组数超千万或单组数据量极大(如user_id对应500万日志)即撑爆内存,mysql被迫落盘;sort_buffer_size仅控制order by排序,不干预group by临时表行为;真正决定内存临时表上限的是tmp_table_size与max_heap_table_size中的较小值,二者必须显式设为相同值(如均设256m)并重启生效,否则仍按默认小值运行;优先优化索引(如建联合索引idx_user_time)、精简字段、分片查询和启用游标流式读取,而非盲目调参。

临时表溢出磁盘不是配置没调够,而是聚合中间状态失控的明确信号——GROUP BY或COUNT(DISTINCT)把分组键和聚合值全塞进内存哈希表,一旦分组数超千万或某组数据量极大(比如一个user_id对应500万条日志),哈希表就撑爆,MySQL被迫写磁盘临时表。
为什么调大sort_buffer_size没用?
sort_buffer_size只管ORDER BY排序,不控制GROUP BY临时表行为。真正决定内存临时表上限的是tmp_table_size和max_heap_table_size中较小的那个值——两者不一致时,你设了512MB,实际可能还是按默认16MB跑。
- 查当前生效值:
SELECT @@tmp_table_size, @@max_heap_table_size; - 必须在
my.cnf的[mysqld]段里显式写两行:tmp_table_size = 256M和max_heap_table_size = 256M - 重启后执行
SHOW VARIABLES LIKE 'tmp_table_size';确认二者数值完全相等 - 别设太高:超过物理内存20%可能引发系统级OOM,尤其并发高时
怎么让GROUP BY不落盘?
靠参数兜底不如让查询本身避开大中间结果。关键看执行计划里有没有Using temporary,以及Created_tmp_disk_tables是否持续上涨。
- 给
GROUP BY字段加联合索引,例如ALTER TABLE logs ADD INDEX idx_user_time (user_id, created_at); - 避免
SELECT *,只取必要字段;特别要避开TEXT/BLOB列参与分组 - 拆大聚合:用主键范围分片,先建临时表存一批
id,再JOIN聚合,每次只处理几万行 - 如果
WHERE条件能下推,别写成GROUP BY后再HAVING过滤
客户端拉全量结果也会触发磁盘溢出?
会。JDBC驱动默认把整个结果集加载进内存,不是数据库端的问题——尤其是带COUNT(DISTINCT)或多层嵌套聚合时,ResultSet一读就崩。
- MySQL连接串必须加:
?useCursorFetch=true&defaultFetchSize=500 - PostgreSQL需显式启用游标:
statement.setFetchSize(500); -
defaultFetchSize只对普通SELECT有效,对INSERT … SELECT或CTE聚合无效 - 检查
SHOW PROCESSLIST里有没有Copying to tmp table状态卡住
最容易被忽略的是:即使tmpdir挂在SSD上,只要Created_tmp_disk_tables还在涨,说明SQL本身还在高频生成大中间结果——换盘只是延缓问题,不是解决。











