group by本身不耗内存,但其触发的哈希聚合需全量缓存分组键及中间状态,大字段(如varchar(2000))导致内存爆炸;有效解法是用sha2、substring_index等确定性表达式替代原始大字段分组。

GROUP BY 本身不“消耗”内存,它触发的哈希聚合过程才是内存杀手——数据库必须把**每个分组键的完整值 + 对应聚合中间状态**全量缓存在内存哈希表里。一个 VARCHAR(2000) 字段,单值占 1.5 KB,一千万个不同值,光键就吃掉 15 GB 内存,根本没机会算 COUNT 或 SUM。
GROUP BY 大字段直接撑爆哈希表
MySQL 和 PostgreSQL 都不会自动截断、压缩或哈希 GROUP BY 字段。你写 GROUP BY description,数据库就原样把整段文本塞进哈希桶;哪怕只 SELECT COUNT(*),也得存全部 description 值用于比对分组边界。
-
EXPLAIN里看不到“大字段警告”,但Extra列出现Using temporary就是明确信号 - JSON 字段、TEXT、长 URL、HTML 片段都属于高危类型,单键体积极易突破几 KB
- PostgreSQL 的
HashAgg节点若显示Spill to disk,说明 work_mem 已经不够,但磁盘排序阶段仍需加载全部分组键进内存
调大 tmp_table_size 或 work_mem 只是拖延崩溃
参数调得再高,也无法改变“分组数 × 单键体积”这个乘法关系。设 work_mem = '1GB' 看似够用,但 50 个并发查询同时执行,就是 50 GB 内存需求,远超 shared_buffers 容量,反而引发系统级 OOM。
- MySQL 中
tmp_table_size和max_heap_table_size取较小值生效,只改一个等于白改 - PostgreSQL 的
work_mem是每个操作独占(如一个查询里有 2 个 GROUP BY,就申请 2 份) -
sort_buffer_size完全无关——它只管 ORDER BY,不干预 GROUP BY 的哈希表分配
真正有效的解法:让分组键变小、变稳定
别硬扛,要重构分组逻辑。核心原则是:用固定长度、确定性、可索引的表达式替代原始大字段。
- 对文本字段,用
GROUP BY SHA2(long_text, 256)(固定 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 条件没下推到索引最左,就会导致全表扫描后再分组,中间结果集爆炸。
- 建联合索引时,把
WHERE字段放最左,GROUP BY字段紧随其后,例如INDEX(status, created_date)对应WHERE status = 'paid' GROUP BY created_date - 用
SELECT COUNT(*) FROM (SELECT 1 FROM t GROUP BY a,b) AS _快速验证分组数是否已超千万,比直接跑全字段安全得多 - 视图里含
GROUP BY或DISTINCT,MySQL 会强制物化整个中间结果集,哪怕你只SELECT * FROM v LIMIT 10
最容易被忽略的是分组键的“实际体积”——不是字段定义长度,而是真实数据平均长度;一个标称 VARCHAR(2000) 的字段,如果平均只存 3 字符,那问题不大;但如果平均存 1800 字符,就非常危险。上线前务必用 SELECT AVG(LENGTH(big_column)) FROM t 实测。










