group by大字段直接撑爆哈希表内存,因数据库将分组键(如varchar(2000)、text)与聚合值全量缓存于内存哈希表,单键几kb×千万级分组即达数十gb;根本解法是用sha2/md5哈希或substring_index等确定性截断替代原始大字段,并避免select*和where索引失效。

GROUP BY 大字段直接撑爆哈希表内存
数据库执行 GROUP BY 时,会把分组键和聚合中间值全量缓存在内存哈希表里。如果分组字段是 VARCHAR(2000)、TEXT 或 JSON 字段,单个键几 KB,一千万不同值就轻松吃掉几十 GB 内存——根本没机会走到聚合计算那步。
常见错误现象:EXPLAIN 里看不到警告,但 Extra 出现 Using temporary 就已是危险信号;查询卡住、OOM killer 杀进程、或日志里反复报 could not resize shared memory segment(PostgreSQL)或 resource_semaphore 等待(SQL Server)。
- 别用
GROUP BY LEFT(long_text, 100):既不减内存,又让索引失效 - 避免
GROUP BY JSON_EXTRACT(data, '$.body')这类原值提取:JSON 字段本身体积大,且无法走函数索引(除非显式建函数索引) - MySQL 8.0+ / PostgreSQL 12+ 才支持函数索引,
SUBSTRING_INDEX(url, '/', 3)这类表达式必须配合函数索引才有效
用确定性哈希或前缀替代原始大字段
真正可落地的解法,是让分组键变小、变稳定,而不是调大 tmp_table_size 或 work_mem —— 后者只是延缓崩溃,不是解决。
对文本字段,改用 GROUP BY SHA2(big_text, 256)(固定 64 字节)或 MD5(big_text)(固定 32 字节),冲突概率在业务可控范围内;对 JSON 字段,先提取关键路径再哈希,例如 GROUP BY SHA2(JSON_EXTRACT(data, '$.user_id'), 256)。
-
SHA2比MD5更安全,但计算开销略高;若只做去重/分组,MD5足够 - 哈希值需加索引:MySQL 中建函数索引
CREATE INDEX idx_hash ON t (SHA2(text_col, 256));PostgreSQL 中用CREATE INDEX idx_hash ON t ((md5(text_col))) - 别在哈希字段上
ORDER BY原始值——哈希后顺序已丢失,如需排序,得额外关联原表
SELECT * 和 WHERE 条件错位放大内存压力
你以为只 GROUP BY 大字段有问题?其实 SELECT * 和不当的 WHERE 条件会让哈希表体积翻倍甚至指数级增长。
SELECT * 导致回表 + 全行加载进临时表;WHERE 条件写在聚合之后(如 HAVING)、或用了非 SARGable 表达式(如 WHERE YEAR(create_time) = 2025),都会迫使数据库先扫描全量数据再过滤。
- 永远只
SELECT真正需要的字段,尤其是避免带大字段(TEXT、JSON) -
WHERE条件必须放在GROUP BY之前,且尽可能用索引列的裸值比较,例如WHERE create_time >= '2025-01-01',而非WHERE DATE(create_time) = '2025-01-01' - 复合索引顺序很重要:
WHERE status = 1 GROUP BY user_id应建(status, user_id),而不是反过来
work_mem / tmp_table_size 不是万能解药
调大 work_mem(PostgreSQL)或 tmp_table_size(MySQL)只能缓解,不能根治。本质问题在于哈希表内存占用 = 分组数 ×(分组键体积 + 聚合值体积)。
MySQL 中 tmp_table_size 和 max_heap_table_size 取较小值生效;PostgreSQL 的 work_mem 是单查询上限,高并发下多个查询争抢,反而触发整体 OOM。
- PostgreSQL:单次查询设
work_mem = '512MB'可能没问题,但 20 个并发就吃掉 10GB,远超物理内存 - MySQL:设
tmp_table_size = 1G,但max_heap_table_size = 64M,实际仍按 64MB 限制 - 即使 fallback 到磁盘(如 PostgreSQL 的
SORTAGG),排序阶段仍需加载全部分组键进内存,不是“自动落盘”
最易被忽略的点:哈希值虽小,但一旦涉及 HAVING 或 ORDER BY 原始字段,就得回查原表——这时候大字段又回来了。所以哈希分组必须和业务逻辑对齐,不能只图 SQL 看着短。











