聚合内存溢出核心是控制哈希表中间状态大小、避免单点倾斜;应优先用主键分片+临时表拆分聚合,而非盲目调大work_mem或内存,因高并发下易引发oom。

聚合内存溢出不是加内存能解决的——核心是控制哈希表中间状态大小,避免单点倾斜。
GROUP BY 为什么一跑就 OOM
数据库执行 GROUP BY 或 COUNT(DISTINCT) 时,会把每个分组键(如 user_id)和对应聚合值(如 SUM(amount))缓存在内存哈希表里。一旦分组数超千万,或某个 user_id 对应 500 万行日志,哈希表就直接撑爆 work_mem(PostgreSQL)或 JVM 堆(某些连接层)。sort_buffer_size(MySQL)对聚合内存几乎没影响,它只管排序,不控哈希。
- PostgreSQL 查当前值:
SHOW work_mem,默认常为 4MB,对千万级分组远远不够 - MySQL 检查执行计划:若出现
Using temporary; Using filesort,说明已放弃索引分组,全量加载进内存 - 快速验证分组爆炸:
SELECT COUNT(*) FROM (SELECT 1 FROM t GROUP BY a, b) AS _,比SELECT * FROM t GROUP BY a, b安全得多
临时表分片比调 work_mem 更可靠
靠调大 work_mem 抗压风险极高:一个查询里有 JOIN + GROUP BY + ORDER BY,三者各自申请一份 work_mem,50 个并发 × 256MB = 12.8GB,可能拖垮整个实例。不如主动拆解。
- 前提:左表有单调主键(如
id或create_time),右表关联字段有索引 - 建临时表:
CREATE TEMPORARY TABLE tmp_ids (id BIGINT PRIMARY KEY) - 分批插入 ID:
INSERT INTO tmp_ids VALUES (100001), ..., (200000)(每次 ≤ 5 万) - 聚合走索引:
SELECT user_id, COUNT(*) FROM orders JOIN tmp_ids USING (id) GROUP BY user_id
这样每次聚合只处理几万行,内存峰值稳定在 50–200MB 区间,可控且可并行。
一款AI工具,主要用于将编码任务调度到本地 OpenAI Codex CLI,支持后台执行、状态轮询以及可交互式回答的澄清问题。适用于 OpenClaw 需要……,适合需要提升相关任务效率的用户。
JDBC 流式读取只救客户端,不救数据库
加 setFetchSize(500) 或 MySQL 连接串加 ?useCursorFetch=true&defaultFetchSize=500,只是让 JDBC 驱动不把全部结果一次性 load 到 JVM 堆里。但数据库服务端仍要算完全部分组、生成完整结果集,再逐批发——服务端内存照样可能爆。
- MySQL:该参数对
INSERT ... SELECT或 CTE 聚合无效 - PostgreSQL:必须显式声明游标:
DECLARE agg_cursor CURSOR FOR SELECT ... GROUP BY ... - 真正压垮服务端的是聚合逻辑本身,不是传输方式
别忽略这三个硬限制
很多调参失败,是因为没看清底层约束:
- MySQL 8.0+ 的 CTE 聚合结果默认不可下推物化,
WITH t AS (SELECT ...) SELECT COUNT(*) FROM t GROUP BY x仍可能 OOM,得加MATERIALIZED提示 - PostgreSQL 的
HASHAGG在work_mem不足时 fallback 到SORTAGG,但它仍需内存排序,不是自动落盘 - 窗口函数(如
ROW_NUMBER() OVER (ORDER BY x))无法下推 WHERE,必须先用子查询过滤再开窗,否则全量排序必爆
最易被忽略的点:分组键的基数和单组数据量,远比总行数更能决定是否 OOM;而这个维度,EXPLAIN 看不出来,得靠 SELECT COUNT(DISTINCT key) 和业务分布预判。










