group by导致磁盘写满的根本原因是结果集超内存阈值后强制落盘到/tmp生成#sql_临时文件;需同步调大tmp_table_size与max_heap_table_size、建匹配联合索引、避免select、用预聚合替代实时分组。

GROUP BY 为什么总在 /tmp 下写 #sql_* 文件
不是查询慢,是磁盘直接写满卡死——GROUP BY 结果集超出内存阈值后,MySQL 强制落盘到 tmpdir(默认 /tmp),生成带 #sql_* 前缀的 MyISAM 或 InnoDB 临时文件。一旦并发高、数据量大、字段宽(比如含 TEXT 或超长 VARCHAR),这些文件会瞬间占满分区。
-
SELECT *+GROUP BY是最常见诱因:把整行数据先捞出来再分组,中间结果体积爆炸 -
WHERE条件太松(如没加时间范围)、GROUP BY字段无索引,导致无法在索引内完成聚合,只能全表扫描后建临时表 -
ORDER BY和GROUP BY字段不一致(比如GROUP BY user_id但ORDER BY created_at),触发Using filesort+Using temporary双重开销
tmp_table_size 和 max_heap_table_size 必须设成一样
只调 tmp_table_size 没用,MySQL 实际取两者中较小值作为内存临时表上限。设成不同值等于白改。
- 查当前值:
SELECT @@tmp_table_size, @@max_heap_table_size; - 安全调法(例如升到 256MB):
SET GLOBAL tmp_table_size = 268435456;和SET GLOBAL max_heap_table_size = 268435456; - 永久生效:在
my.cnf的[mysqld]段加两行,重启或mysqladmin reload - 别超限:这两个值总和不能超过
innodb_buffer_pool_size的 25%~30%,否则缓冲池被挤占,整体更慢
EXPLAIN 看见 Using temporary 就得改 SQL
参数调再大,只要执行计划里出现 Using temporary,说明查询结构本身逼 MySQL 必须建临时表——这时调参只是延缓问题,不是解决。
- 给
GROUP BY字段建联合索引,且顺序要匹配:比如GROUP BY status, user_id,索引就得是(status, user_id),不是单列status索引 - 避免跨表
GROUP BY:字段必须全部来自同一张表,否则必走临时表 - 函数分组(如
GROUP BY DATE(created_at))必须用函数索引:CREATE INDEX idx_date ON t ((DATE(created_at)))(MySQL 8.0+) - 删掉
SELECT *,只取分组和聚合需要的字段;HAVING里别放子查询或复杂计算,条件尽量前移到WHERE
高并发百万级数据别硬扛 GROUP BY
实时跑 GROUP BY 在并发 > 50 QPS、数据量 > 百万行时,本质上就是反模式。磁盘临时表只是表象,根本问题是没做查询分层。
- 用物化视图(PostgreSQL)或定时汇总表(MySQL)替代:比如按天预聚合,查时直接读
daily_stats表 - 分时段剪枝:
WHERE created_at BETWEEN '2026-06-01' AND '2026-06-30',再UNION ALL结果,避免单次处理全量 - 清理残留:
find /tmp -name "#sql_*" -mmin +60(只删一小时前的),但务必先确认无活跃连接:mysql -e "SHOW PROCESSLIST;" | grep -E "(Copying|Sending)"
真正卡住系统的,往往不是某条 SQL 写得不够“优雅”,而是它在没有索引支撑、没有范围剪枝、没有结果裁剪的前提下,把几百万行原始数据全拖进内存再分组——这时候调参只是给火上浇油。











