work_mem过小会导致排序/哈希操作落盘,表现为EXPLAIN中Sort Method: external merge Disk或Hash Join写磁盘;需按公式(总内存−shared_buffers)/max_connections/2合理设置,并验证pg_reload_conf()生效及监控pg_stat_progress_sort等指标。

work_mem 设置过小会导致排序/哈希操作频繁写临时文件
当你在 EXPLAIN ANALYZE 中看到 Sort Method: external merge Disk: XXXkB 或 Hash Join: using 123456kB of memory, 789kB on disk,说明当前 work_mem 不足以支撑该操作的内存需求,PostgreSQL 已主动落盘。这不是报错,但 I/O 开销会陡增——尤其在 SSD 以下介质上,性能可能下降 3–10 倍。
根本原因不是“排序本身慢”,而是 PostgreSQL 对每个独立排序或哈希操作单独分配 work_mem,而一个查询可能同时触发多个(如嵌套窗口函数 + GROUP BY + ORDER BY + JOIN),总内存消耗是 work_mem × 并发操作数,极易超出预期。
- 常见触发场景:含
ORDER BY的分页查询、DISTINCT去重、GROUP BY聚合、HASH JOIN、窗口函数(尤其是未分区/未排序的SUM() OVER (ORDER BY ...)) - 注意:CTE 默认物化(除非用
NOT MATERIALIZED)也会额外占用一份work_mem -
work_mem单位是 KB,默认值通常为4MB(即 4096),对现代服务器明显偏低
怎么算出安全又高效的 work_mem 初始值
不能直接设成 1GB —— 那样在 max_connections = 300 的系统上,仅排序就可能吃掉 300GB 内存。必须结合连接数和可用物理内存做约束计算。
推荐公式(兼顾安全与效率):work_mem = (总内存 - shared_buffers) / max_connections / 2
- 例如:128GB 物理内存,
shared_buffers = 32GB,max_connections = 300→(128 - 32) * 1024 / 300 / 2 ≈ 16384 KB(即 16MB) - 这个值允许单个排序操作使用最多 16MB,300 个并发连接满载时,理论最大排序内存开销约 4.8GB(未计入哈希等其他操作),留足余量给 OS 和 WAL
- 如果业务以短连接+高并发为主(如 Web API),可再乘以 0.7;若多为长连接分析型查询,可乘以 1.2~1.5
为什么调大 work_mem 后还是有 temp file?
即使你把 work_mem 设到 64MB,仍看到 external merge Disk,大概率不是参数没生效,而是以下任一情况:
- 你改的是
postgresql.conf,但没执行SELECT pg_reload_conf();或重启服务(动态参数需 reload) - 会话级覆盖了全局值:
SET work_mem = '8MB';—— 检查是否有应用连接池(如 PgBouncer)或 ORM 自动设置 - 查询中用了并行执行(
parallel_workers > 0),每个 worker 进程都独立申请work_mem,实际总消耗翻倍甚至更高 -
maintenance_work_mem和autovacuum_work_mem是独立参数,CREATE INDEX或VACUUM不受work_mem控制,别混淆 - 临时文件路径所在磁盘已满,或
temp_tablespaces指向的表空间不可写,PostgreSQL 会降级写入默认pg_default,但日志仍显示 “temporary file”
验证和持续观察的关键指标
光看配置没用,得盯运行时行为。最直接的方式是打开临时文件记录:
ALTER SYSTEM SET log_temp_files = 0; -- 记录所有 > 0 字节的临时文件 SELECT pg_reload_conf();
然后复现问题 SQL,查日志确认是否还有落盘;更轻量的方式是监控统计视图:
-
pg_stat_database.temp_files:累计创建的临时文件数(按库维度) -
pg_stat_database.temp_bytes:这些临时文件总大小 -
pg_stat_progress_sort:实时查看哪些会话正在排序、是否已落盘(字段datid,pid,sort_method)
真正难处理的不是单次溢出,而是高频小文件(比如每秒几十个 1MB 临时文件)——这往往意味着 work_mem 设得太保守,或者查询结构本身存在冗余排序,得回代码层优化,而不是一味加内存。

















