PostgreSQL 16中GROUP BY慢主因是work_mem不足致落盘或聚合函数不满足Partial Mode而无法并行;需用EXPLAIN (ANALYZE, BUFFERS)查Disk Usage>0确认落盘,并确保string_agg(无ORDER BY)、sum等支持并行的函数配合调参(如max_parallel_workers_per_gather、min_parallel_table_scan_size、work_mem)才能真正生效。

PostgreSQL 16 中的 GROUP BY 慢,八成不是 SQL 写错了,而是内存不够或并行没真正跑起来——work_mem 不足导致落盘,或聚合函数不满足 Partial Mode 条件,让并行直接被跳过。
为什么 GROUP BY 查询实际执行时还在用磁盘?
根本原因是 work_mem 设置太小,哈希聚合或排序被迫写临时文件。别靠猜,用实测数据确认:
- 运行
EXPLAIN (ANALYZE, BUFFERS),重点看HashAggregate或Sort节点下的Disk Usage字段 —— 只要大于 0,就是落盘了 - 查
pg_stat_progress_hash视图,对比htuples和当前work_mem:若实际哈希桶数远超内存能容纳量(比如设了'8MB',但视图显示已用24MB),说明严重不足 - 日志里搜
writing to disk due to insufficient memory,出现即铁证
哪些 GROUP BY 场景能真正触发并行?
并行不是自动开的,只对「可分片 + 可合并」的聚合生效。PostgreSQL 16 明确支持的组合极少,且极易踩坑:
- ✅ 支持并行:
string_agg(col, ',')(无ORDER BY)、array_agg(col)、json_agg(col)、sum()/max()/min()/count()(非DISTINCT) - ❌ 不支持并行:
count(distinct x)、avg(x)、string_agg(x, ',' ORDER BY y)、任何含volatile函数(如now())或FOR UPDATE的查询 - ⚠️ 常见误判:即使开了
max_parallel_workers_per_gather,只要底层聚合不支持Partial Mode,EXPLAIN 里就绝不会出现Gather+Partial Aggregate组合
怎么调参让并行 GROUP BY 真正生效?
默认参数对中小表基本无效,必须手动压低门槛。但要注意:这些是会话级设置,别改全局配置。
- 先放开 worker 数量:
SET max_parallel_workers_per_gather = 4;(建议 ≤ CPU 核心数) - 降低并行启动阈值:
SET min_parallel_table_scan_size = '1MB';(生产环境慎用,测试可用) - 压低成本估算:
SET parallel_setup_cost = 2;和SET parallel_tuple_cost = 0.01; - 同步加大
work_mem:SET LOCAL work_mem = '32MB';(每个 worker 独占一份,总内存消耗 ≈(1 + max_parallel_workers_per_gather) × work_mem)
视图里套 GROUP BY 为什么越优化越慢?
因为 PostgreSQL 默认把视图展开为子查询,一旦视图定义含 GROUP BY 或 UNION,外层 WHERE 很难下推,结果是先全量聚合再过滤。
- 典型症状:
EXPLAIN里出现Subquery Scan on your_view_name,而不是直接看到基表名 - 真正能提速的写法:视图必须是“固定口径预聚合”,比如
CREATE VIEW sales_by_day AS SELECT date_trunc('day', ts), region, SUM(val) FROM logs WHERE ts >= '2025-01-01' GROUP BY 1, 2;—— 时间下限写死、字段显式列出、无ORDER BY - 建完立刻加索引:
CREATE INDEX idx_sales_by_day ON sales_by_day (date_trunc, region);(仅 PostgreSQL 支持视图索引)
最易被忽略的一点:并行和 work_mem 是两套独立机制。即使并行跑起来了,如果单个 worker 的 work_mem 不够,它内部的哈希聚合仍会落盘——所以必须同时调参,且用 EXPLAIN (ANALYZE, BUFFERS) 验证每个节点的内存使用情况。

















