为什么你的 PostgreSQL 不断耗尽连接

发布: (2026年3月18日 GMT+8 01:31)
4 分钟阅读
原文: Dev.to

Source: Dev.to

理解连接限制

PostgreSQL 强制执行最大并发连接数,由 max_connections 设置决定。典型的默认值为:

  • 小型/基础层级: 50–100 个连接
  • 通用层级: 100–200 个连接

当应用尝试打开的连接数超过此限制时,PostgreSQL 会返回 TooManyConnectionsError。数据库本身仍然在运行,只是没有空间再接受新连接。

为什么单个连接池不足以解决问题

即使使用了连接池,创建多个池也会快速耗尽限制。

典型应用中的情况

class DatabaseClient:
    def __init__(self):
        self.pool = create_pool(min=2, max=10)   # each instance gets its own pool

如果你的服务生成多个组件,每个组件都实例化 DatabaseClient

组件创建的池数量每个池的连接数
健康检查器110
依赖检查器110
每个工作者(例如 6)6每个 10(共 60)
总计880

运行两个容器会使这个数字翻倍到 160 个连接,轻易超过 100 的 max_connections,从而触发错误。

解决方案:单一共享池

在模块或进程级别创建 一个池,让所有 DatabaseClient 实例共享它。

# shared_pool.py
_shared_pool = None

def get_shared_pool():
    global _shared_pool
    if _shared_pool is None:
        _shared_pool = create_pool(min=2, max=5)   # configure as needed
    return _shared_pool
# client.py
from shared_pool import get_shared_pool

class DatabaseClient:
    def get_connection(self):
        return get_shared_pool().acquire()

现在,无论创建多少个 DatabaseClient 对象,它们都共享同一组受限的连接。

需要注意的陷阱

  1. close() 陷阱
    如果客户端的 close() 方法关闭了共享池,后续的调用将会失败。让单个实例的 close() 成为空操作,只在整个进程退出时关闭池。

  2. 健康检查的连锁效应
    当数据库耗尽连接时,健康检查查询也会失败。编排系统可能会重启容器,这会创建新池,进一步恶化问题。

  3. 可配置的池大小
    使用环境变量(例如 PG_POOL_MAX_SIZE=5)来调整池大小,而无需重新部署。

  4. 部署前先做简易计算

    pool_max_size × max_replicas < max_connections - admin_headroom

    示例

    • 池最大大小:5
    • 跨服务的总副本数:10 → 5 × 10 = 50
    • max_connections:100
    • 管理员预留空间:20 → 100 − 20 = 80

    因为 50 < 80,配置是安全的。如果不满足不等式,请减小池大小或引入专用的连接池工具,如 PgBouncer

推送更改前的检查清单

  • ✅ 在模块/进程级别使用单一共享池。
  • ✅ 通过环境变量使池大小可配置。
  • ✅ 确保单个实例的 close() 不执行任何操作。
  • ✅ 仅在进程关闭时关闭共享池。
  • ✅ 为所有服务验证连接数计算。
  • ✅ 运行语法和单元测试,确保代码正常工作。

TL;DR

  • 多个池 = 连接耗尽。
  • 单一共享池 = 稳定、可预测的使用。

认识到问题通常是 池太多——而不是没有池——可以把凌晨 1 点的慌乱变成可控的局面。希望这能帮助那些刚收到 “数据库连接耗尽” 警报的同事。 🙂

0 浏览
Back to Blog

相关文章

阅读更多 »

阻止多租户应用中的数据泄漏

为什么仅靠应用逻辑不足以满足需求:数据库级行级安全的案例 你已经构建了一个强大的多租户 SaaS。你已经实现了 tenant_id f…