Hash Join写磁盘是整桶刷出而非部分落盘,确认work_mem不足需先验证EXPLAIN(ANALYZE,BUFFERS)中Disk Usage非零、pg_stat_progress_hash比值超3、排除数据倾斜与类型不一致;work_mem按操作节点独享,中小负载建议32MB起步,须用SET LOCAL临时调整。

Hash Join 写磁盘不是“缓存不够就慢慢写一点”,而是整桶刷出——一旦 work_mem 不足,PostgreSQL 会把整个哈希桶(bucket)序列落盘,后续每次探查(probe)都得重新读磁盘,性能从纳秒级指针跳转变成毫秒级随机寻道,放大超 10⁵ 倍。
怎么确认真是 work_mem 不够,而不是其他问题?
别看到 Hash Join 就调内存,先排除更常见的假阳性:
-
EXPLAIN (ANALYZE, BUFFERS)中对应Hash节点的Disk Usage字段为0→ 根本没落盘,work_mem不是瓶颈 -
pg_stat_progress_hash视图里hash_probe_total_buckets / hash_buckets_used比值 > 3 → 桶严重稀疏,说明work_mem太小被迫建了大量空桶,内存被浪费而非不足 -
SELECT key, COUNT(*) FROM table GROUP BY key ORDER BY 2 DESC LIMIT 5→ 若 TOP1 频次 > 平均值 × 10,属数据倾斜,加内存无效 - JOIN 字段类型不一致,比如
CAST(u.id AS TEXT) = o.user_id→ 强制转换导致索引失效、优化器误判,硬上Hash Join
work_mem 设多大才真起作用?
它不是给整个查询用的,而是每个操作节点(一个 Hash Join、一个 Sort、一个 GROUP BY)各自独享的上限:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- 估算下限:对驱动表 Join 键粗略计算 ——
(行数 × (键长度 + 8)) × 1.5(1.5 是哈希填充因子预留) - 中小负载建议从
'32MB'起步;高分析负载可试'64MB';超过'128MB'必须评估并发压力 - 临时调优务必用
SET LOCAL work_mem = '64MB',仅当前事务生效;全局改易引发 OOM - 注意并行:若
max_parallel_workers_per_gather = 4,单个Hash Join最多消耗 4 ×work_mem
为什么调了 work_mem 还是写磁盘?
常见静默失败点:
- 写成
'64M'(缺B)→ PostgreSQL 静默转为 64 字节,等于没设 - 连接池如
pgbouncer拦截SET命令 → 确认其ignore_startup_parameters未屏蔽运行时参数 - 同一查询含多个 Hash/Sort/DISTINCT → 总内存消耗是叠加的,但你只给了单个操作额度
-
shared_buffers或effective_cache_size调再高也没用 —— 它们不参与哈希表构建
Hash Join,哪怕每个只吃 32MB,也意味着至少 96MB 实时内存占用;而 pg_stat_progress_hash 只反映主进程状态,worker 落盘了你也看不到。

















