先确认Hash Join写磁盘是否真因work_mem不足:用EXPLAIN(ANALYZE,BUFFERS)查Disk Usage是否非零;若为0则未落盘,无需调优;再查pg_stat_progress_hash比值>3或TOP1频次>均值×10,分别定位桶稀疏或数据倾斜。

Hash JOIN写磁盘了,先确认是不是work_mem真不够
看到日志里反复出现 writing to disk due to insufficient memory,别急着调大 work_mem。真正拖慢 Hash Join 的,常是数据倾斜、统计信息过期,或执行计划误选了 Hash 而非 Merge。第一步必须用 EXPLAIN (ANALYZE, BUFFERS) 跑一次实际查询,重点看 Hash 节点的 Disk Usage 字段——如果为 0,说明根本没落盘,调 work_mem 没意义。
查pg_stat_progress_hash和TOP1频次,定位根因是倾斜还是桶稀疏
若 Disk Usage 非零,再查 pg_stat_progress_hash(PostgreSQL 12+):
-
hash_probe_total_buckets / hash_buckets_used比值 > 3?说明哈希桶严重稀疏,大概率是work_mem太小被迫建了过多桶,内存被浪费而非不足 - 同时跑
SELECT key, COUNT(*) FROM logs GROUP BY key ORDER BY 2 DESC LIMIT 5,检查连接键(比如user_id或session_id)分布:若 TOP1 频次 > 平均值 × 10,就是典型数据倾斜——加内存也救不回来,该考虑预过滤或换Merge Join
work_mem设多少才合理?从SET LOCAL起步,别碰全局
work_mem 是每个操作(一个 Hash Join、一个 Sort、一个 DISTINCT)能独占的上限,不是整个查询或会话的总和。一个含 Hash Join + Sort 的查询,实际内存开销是两倍 work_mem;开了并行(max_parallel_workers_per_gather = 4),最多吃掉 4 倍。
- 估算下限可粗略按驱动表算:
(行数 × (Join 键长度 + 8)) × 1.5(1.5 是哈希填充因子) - 推荐起点:
SET LOCAL work_mem = '4MB',逐步试到'16MB'→'32MB'→'64MB',每次只改当前事务 - 绝对避免全局设置:
max_connections = 200时,work_mem = '64MB'理论峰值达 12.8GB,极易触发 OOM 或 swap - 单位必须写对:
'64MB'或67108864,错写成'64M'会被静默转成 64 字节,直接崩
为什么调了work_mem还是写磁盘?并行、嵌套、单位全在偷吃内存
即使数值看起来够,Hash Join 仍可能落盘,问题往往出在并发细节上:
- 同一查询含 Hash Join + Sort + DISTINCT?三者各自申请
work_mem,总开销是三倍 - 用了并行查询?每个 worker 进程都独立申请
work_mem,但pg_stat_progress_hash只反映主进程状态,容易误判 - 嵌套 Hash Join(比如子查询里又有一个 Hash)?每个层级都算一个独立操作
-
shared_buffers和effective_cache_size对 Hash Join 完全无效——它们不参与哈希表构建,调了也没用
真正卡住性能的,往往是这些叠加效应和单位笔误,而不是“内存不够”这个表面结论。

















