GROUP BY大字段直接撑爆哈希表内存,因数据库将分组键(如VARCHAR(2000)、TEXT)与聚合值全量缓存于内存哈希表,单键几KB×千万级分组即达数十GB;调大tmp_table_size或work_mem仅延缓崩溃,根本解法是用SHA2/MD5哈希、SUBSTRING_INDEX等确定性截断替代原始大字段,并避免SELECT*和WHERE索引失效。

GROUP BY大字段直接撑爆哈希表内存
数据库执行 GROUP BY 时,并不是先分组再挑字段,而是把**分组键 + 所有聚合中间值**一起缓存在内存哈希表里。如果 GROUP BY 字段本身是 VARCHAR(2000)、TEXT 或带 JSON 的列,单个键就可能占几 KB —— 一千万个不同值,光键就吃掉几十 GB 内存,根本没机会走到聚合计算那步。
- MySQL 不会压缩或截断 GROUP BY 字段内容,原样塞进哈希表;PostgreSQL 同样按实际字节长度分配空间
- 哪怕只
SELECT COUNT(*),只要GROUP BY big_column,哈希表仍要存全部big_column值做比对 -
EXPLAIN里看不到“大字段警告”,但Extra出现Using temporary就已是危险信号
为什么加 tmp_table_size 或 work_mem 也救不了
调大 tmp_table_size(MySQL)或 work_mem(PostgreSQL)只能延缓崩溃,不能改变本质:哈希表内存占用 = 分组数 ×(分组键体积 + 聚合值体积)。字段越大,单个桶越重,阈值还没到,内存已耗尽。
- MySQL 中
tmp_table_size和max_heap_table_size取较小值生效,设 1GB 但另一项是 64MB,实际仍按 64MB 限制 - PostgreSQL 的
work_mem = '512MB'是单查询上限,高并发下多个查询争抢,反而触发整体 OOM - 即使 fallback 到磁盘(如 PostgreSQL 的
SORTAGG),排序阶段仍需加载全部分组键进内存,不是“自动落盘”
替代方案:用确定性哈希或前缀代替原始大字段
真正可落地的解法,是让分组键变小、变稳定,而不是硬扛。
- 对文本字段,改用
GROUP BY SHA2(big_text, 256)或MD5(big_text)—— 哈希值固定 32/64 字节,冲突概率可控 - 对 JSON 字段,提取关键路径后哈希:
GROUP BY SHA2(JSON_EXTRACT(data, '$.user_id'), 256) - 若必须按前缀分组(如 URL 域名),用
GROUP BY SUBSTRING_INDEX(url, '/', 3),但得确保该表达式能走函数索引(MySQL 8.0+ / PG 12+) - 绝对避免
GROUP BY LEFT(long_text, 100)这类无索引操作——它既不减内存,又让索引失效
最容易被忽略的隐性放大器:SELECT * + WHERE 条件错位
你以为只 GROUP BY 大字段有问题?其实 SELECT * 和 WHERE 条件设计不当,会让哈希表体积翻倍甚至指数级增长。
-
SELECT *导致回表 + 全行加载进临时表,哪怕分组字段有索引,单行体积也可能从 100B 涨到 5KB - 索引是
(status, content),但写成WHERE content LIKE '%abc%' GROUP BY status——LIKE前导通配让索引失效,全表扫 + 全字段进哈希表 - 用
COUNT(DISTINCT big_column)会额外建一层去重哈希表,内存消耗是普通GROUP BY的 2–3 倍
真正卡住的从来不是结果集大小,而是数据库在算出结果前,不得不把所有分组键和中间状态全 load 进内存 —— 键越长、越散、越不可预测,这一步就越危险。

















