group by慢主因常是work_mem不足致排序或哈希聚合落盘;确认方法为explain (analyze, buffers)查sort/hashaggregate节点的disk usage>0,或查pg_stat_progress_sort/hash视图及日志中“writing to disk”提示。

GROUP BY 慢,大概率不是 SQL 写得差,而是 work_mem 不够用,导致排序或哈希聚合被迫落盘。 一旦看到执行计划里出现 Sort 节点带 disk 字样,或 HashAggregate 的 Disk Usage 非零,基本可以锁死是内存瓶颈。
怎么确认 GROUP BY 正在用磁盘而不是内存?
别猜,直接看执行计划的实测数据:
- 运行
EXPLAIN (ANALYZE, BUFFERS),重点找Sort或HashAggregate节点下的Memory Usage和Disk Usage字段——只要Disk Usage> 0,就是落盘了 - 查
pg_stat_progress_hash(v12+)或pg_stat_progress_sort视图,观察sort_bytes是否远超work_mem设置值(比如设了 4MB,但实际用了 12MB) - 日志里搜
writing to disk due to insufficient memory,出现就说明 PostgreSQL 主动降级了
work_mem 设多大才合适?
它不是越大越好,也不是越小越省事。关键要理解:它是「每个操作」独占的上限,不是整个查询或整个连接的总和。
- 一个含
GROUP BY的查询,可能同时触发多个排序(如子查询、窗口函数、ORDER BY),每个都单独吃掉一份work_mem - 并发 50 个连接,
work_mem = '64MB',理论峰值内存占用就是 50 × 64MB ≈ 3.2GB——还没算shared_buffers和系统缓存 - 推荐起点:从
SET LOCAL work_mem = '8MB'开始测;如果仍落盘,逐步加到'32MB'、'64MB';超过'128MB'就得谨慎,尤其在高并发场景 - 估算下限可参考:对分组键做
SELECT COUNT(*) FROM table GROUP BY key,若结果集行数约 10 万,键平均长度 20 字节,粗略需内存 ≈ 100000 × (20 + 8) × 1.5 ≈ 4.2MB —— 这就是work_mem的保底值
该在哪儿改 work_mem?全局?会话?还是语句级?
优先用会话级或事务级,避免“一人生病全家吃药”:
- 全局改(
postgresql.conf)风险最高:所有连接、所有操作无差别生效,容易引发 OOM 或 swap 频繁 - 更安全的是在业务逻辑入口处加一句:
SET LOCAL work_mem = '32MB',仅对当前事务生效,且不会污染其他查询 - 如果只有一条慢 SQL 需要救急,甚至可以直接在查询前加:
BEGIN; SET LOCAL work_mem = '64MB'; SELECT ... GROUP BY ...; COMMIT; - 绝对别碰
shared_buffers或effective_cache_size来“曲线救国”——它们不参与GROUP BY的中间结果构建,改了也没用
真正卡住 GROUP BY 性能的,往往不是算法本身,而是内存没配对。很多人调完 work_mem 后发现查询快了 3–10 倍,却没意识到这背后是磁盘 I/O 和内存访问的量级差异。最容易被忽略的一点:同一个查询,在不同并发压力下,work_mem 的最优值可能完全不同——必须结合 max_connections 和实际负载一起看。










