由 John Doe 四月 16, 2026
你有没有被 PostgreSQL 的 OOM 折磨过?数据库强制重启,所有连接全断。排查半天,发现罪魁祸首只是一个跑飞了的复杂查询,或者一个写崩了的存储过程。

当 PostgreSQL 运行在 Linux 系统上时,单个失控会话的内存暴涨往往是触发 OOM Killer 的头号元凶,尤其是运行 PostGIS 空间分析、复杂存储过程或大数据量聚合的场景。这些会话会持续申请内存直到耗尽系统资源,最终导致 Linux 内核随机终止进程,一旦命中 PostgreSQL 主进程(postmaster),整个数据库将强制重启,所有连接中断,业务遭受严重影响。
下面来给大家介绍一种解决该问题的最优方案:操作系统级别的 ulimit 内存限制,它能让超标会话优雅失败,而非拖垮整个数据库集群。
为什么其他方案都不够彻底?
在介绍 ulimit 方案之前,我们先明确常见替代方案的局限性,这也是社区最终选择 ulimit 的核心原因:
1. 加内存/扩容:只能延迟问题,无法根治。随着业务数据量增长,总会出现更大的查询或事务突破现有内存上限。
2. 调优 PostgreSQL 内存参数:work_mem、hash_mem_multiplier等只是单操作软限制,PostgreSQL 可以选择忽略,且完全无法限制单个会话的总内存使用。
3. 内核vm.overcommit_memory=2:禁用内存过量提交虽能降低 OOM 概率,但会导致大量内存闲置,资源利用率大幅下降。
4. 保护主进程 OOM 评分:将postmaster的oom_score_adj设为 -1000 只能保证主进程不被杀,但失控会话仍会耗尽内存,导致系统其他关键进程被误杀,最终服务依然不可用。
ulimit 方案的核心原理
Linux 的ulimit命令用于限制进程的系统资源使用,其中-v参数(限制虚拟地址空间大小) 在 Linux 上对 PostgreSQL 效果最佳。
其核心逻辑非常简单:
1. 在启动PostgreSQL主进程(postmaster)之前,通过ulimit设置单个进程的虚拟内存上限。
2. 所有由postmaster派生的子进程(即每个数据库会话)都会自动继承这个限制。
3. 当单个会话的总内存申请(包括 PostgreSQL 自身的palloc和第三方扩展的malloc)超过该上限时,操作系统会直接拒绝内存分配,返回ENOMEM错误,PostgreSQL 日志中会出现类似ERROR: out of memory的错误。
4. PostgreSQL 收到该错误后,会优雅回滚当前事务并终止该会话,其他正常会话和主进程完全不受影响。
与 OOM Killer 的暴力终止不同,这种方式会给客户端返回明确的 “out of memory” 错误信息,便于开发人员快速定位问题 SQL,而非面对数据库重启的黑盒。
具体配置步骤
临时测试配置(重启失效)
适用于快速验证方案效果,先在测试环境执行:
# 限制单个进程虚拟内存为 2GB(单位:KB)
ulimit -v 2097152
# 以当前 shell 的资源限制启动 PostgreSQL
su - postgres -c "/usr/pgsql-18/bin/postgres -D /var/lib/pgsql/18/data"
永久生效配置(Systemd 系统,推荐)
绝大多数现代 Linux 发行版使用 Systemd 管理服务,需修改 PostgreSQL 的 service 文件:
# 编辑 PostgreSQL 服务文件(路径一般为:/usr/lib/systemd/system/postgresql-18.service)
systemctl edit postgresql-18.service
在[Service]段添加以下配置:
# 限制单个进程虚拟内存为 2GB(单位:字节,也可直接写 2G)
LimitAS=2147483648
保存后重载配置并重启服务:
systemctl daemon-reload
systemctl restart postgresql-18
总结
ulimit 方案具有操作系统级别的准确性、实现的简单性和对第三方扩展的完美支持,是目前解决单会话内存溢出问题的最佳方法。它将故障影响范围从整个数据库缩小到单个失控会话,极大提升了系统的稳定性和可维护性。
从任何角度来看,操作系统强制的内存限制也比 PostgreSQL 内部实现要好得多。对于运行大量分析类查询的数据库,配置 ulimit 内存限制应当是上线前的必做操作。