group by 撑爆内存是因为将所有分组键值及聚合中间状态全量缓存在内存哈希表中,分组数过多或键值过大(如大字段、无索引表达式)导致oom;需同步调大且相等 tmp_table_size 与 max_heap_table_size,并确保索引匹配 group by 顺序。

GROUP BY 为什么直接撑爆内存
数据库执行 GROUP BY 时,并不是“边分组边丢弃”,而是把**所有分组键值 + 对应的聚合中间状态**(如 COUNT 计数器、SUM 累加值、COUNT(DISTINCT) 的哈希集合)全部缓存在内存哈希表里。一旦分组数超千万,或某一分组数据量极大(比如一个 user_id 对应 400 万行日志),哈希表就直接吃光可用内存。
常见错误现象:
- MySQL 报错
ERROR 1038 (HY001): Out of sort memory或连接突然断开(Lost connection) - PostgreSQL 报
out of memory,EXPLAIN (ANALYZE, BUFFERS)显示Hash Agg节点下有Spill to disk - 执行计划中出现
Using temporary,且Created_tmp_disk_tables持续上涨
tmp_table_size 和 max_heap_table_size 必须相等
MySQL 不靠 sort_buffer_size 控制 GROUP BY 内存——它只管 ORDER BY 排序。真正决定“能用多少内存建临时表”的,是 tmp_table_size 和 max_heap_table_size 中的较小值。两者不一致,等于白调。
实操建议:
- 查当前值:
SELECT @@tmp_table_size, @@max_heap_table_size; - 永久生效:在
my.cnf的[mysqld]段写两行,例如:tmp_table_size = 268435456和max_heap_table_size = 268435456(即 256MB) - 重启后确认:
SHOW VARIABLES LIKE 'tmp_table_size';,两个值必须完全相同 - 别设太高:超过物理内存的 20%~25%,高并发下极易触发系统级 OOM
大字段 GROUP BY 是最隐蔽的内存炸弹
用 VARCHAR(2000)、TEXT 或 JSON 字段做 GROUP BY,单个键可能占几 KB。一千万个不同值,光键就吃掉几十 GB 内存——还没开始算聚合,哈希表已崩。
容易踩的坑:
-
SELECT COUNT(*) FROM t GROUP BY long_text看似只统计,但 MySQL 仍需把每个long_text值全量存进哈希表比对 -
GROUP BY LEFT(long_text, 100)这类表达式无法走索引,且不减内存占用 -
EXPLAIN不会警告“字段太大”,但只要出现Using temporary,就是危险信号
替代方案:
- 改用确定性哈希:
GROUP BY SHA2(long_text, 256)(固定 64 字节) - 提取关键路径再哈希:
GROUP BY SHA2(JSON_EXTRACT(data, '$.user_id'), 256) - 确保函数表达式能走索引(MySQL 8.0+ 支持函数索引,需显式创建)
索引失效比参数没调够更致命
即使你把 tmp_table_size 设到 1GB,只要执行计划里出现 Using temporary; Using filesort,说明 MySQL 已放弃索引,全量加载数据进内存再分组——参数只是延缓崩溃,不是解决问题。
关键检查点:
- 联合索引顺序是否严格匹配
GROUP BY字段顺序?例如索引是(status, user_id),那GROUP BY status, user_id可走索引,GROUP BY user_id, status就不行 - WHERE 条件字段是否放在索引最左?理想结构是:
WHERE 等值字段 → GROUP BY 字段 → SELECT 聚合字段 - 避免在
GROUP BY中混用函数:GROUP BY DATE(created_at)或SUBSTRING(name, 1, 5)会让索引完全失效
真正难处理的,是那些没报错但悄无声息吃光内存的查询——它们往往藏在视图、CTE 或嵌套子查询里,执行路径早已失控。










