PostgreSQL 连接数耗尽:连接池只有 10,为什么仍报 too many clients
连接池 max 只约束一个池。统计数据库全局连接,计算应用实例的连接预算,检查长事务及连接是否归还,再决定怎样调整容量。
PostgreSQL 返回 sorry, too many clients already,你的应用却明明把连接池 max 设成了 10。先统计整个数据库实例的连接,再盘点所有应用进程和连接池。max: 10 约束的是一个池,并不是整个数据库实例只会有 10 个客户端。
先分清数据库满了,还是自己的池在排队
数据库返回的 too many clients 表示新的后端连接受到服务器连接限制。应用侧等待取连接超时,则可能是池内连接都在忙、未归还,或者事务长时间占着连接。这两个问题可能同时出现,但需要不同证据。
node-postgres 的一个池达到上限后,后续取连接通常等待空闲连接;“忘记归还”并不意味着这个池可以无限突破 max。更常见的组合是每个请求新建一个池、每个进程各有多个池,或直连客户端绕过了池。
从保留的管理连接查看下面这些设置和状态。SQL 只读取信息:
SHOW max_connections;
SHOW superuser_reserved_connections;
SELECT datname, usename, application_name, client_addr,
state, count(*) AS connections
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY datname, usename, application_name, client_addr, state
ORDER BY connections DESC;
第一组查询看服务器限制,第二组看谁占用了连接。连接限制属于数据库实例,不只属于某一个逻辑数据库,所以查询不要一开始就只筛你的数据库。统计视图的可见细节取决于权限;需要有相应管理或统计权限,不能把看不到其他会话信息当作其他连接不存在。
PostgreSQL 16 及以上还可能配置 reserved_connections,这部分有自己的使用权限。普通应用能使用的容量不应简单等同于 max_connections,还要考虑保留连接和你为管理操作留的余量。旧版本不要直接执行它不支持的 SHOW 项。
把每个进程的池都计入预算
假设一个部署有 8 个进程,每个进程建两个 max: 10 的池,潜在上限就是 160。这个数字只是预算示例,实际池可能按需建连接,不代表启动后立即占满 160 个。后台任务、管理工具、迁移脚本和其他应用还会使用同一个数据库实例。
盘点时至少找出这些边界:容器副本数、每个容器的 worker 数、每个 worker 创建多少个池,以及滚动升级期间旧新进程是否同时存在。把池初始化放在合适的进程级模块中,避免在每个请求或查询中执行 new Pool()。
如果近期只是增加了应用副本,也要同步调整每个副本的连接预算。不能让每个实例分别按数据库总容量设置自己的池上限,再期望服务器自动分配。
查询完成后,连接有没有回到池里
单条互不关联的查询可以使用 pool.query();它负责取得和归还连接。显式取出客户端时,归还操作要覆盖成功和失败路径。例如下面的片段假设 pool 已在进程中创建:
const client = await pool.connect();
try {
const result = await client.query('SELECT 1');
// 在这里使用 result
} finally {
client.release();
}
事务里的操作必须使用同一客户端,不能在 BEGIN 后把后续 SQL 分散给任意 pool.query()。处理完事务再归还;错误时回滚,并按客户端状态决定是否销毁损坏连接。长驻服务通常在进程关闭时结束池,不要在每个 HTTP 请求后调用 pool.end()。
进一步检查长时间持有连接的会话:
SELECT pid, usename, application_name, state,
now() - xact_start AS transaction_age,
now() - query_start AS query_age,
wait_event_type, wait_event
FROM pg_stat_activity
WHERE backend_type = 'client backend'
AND pid <> pg_backend_pid()
ORDER BY xact_start NULLS LAST, query_start NULLS LAST;
idle 可能只是正常连接池保留的空闲连接,不能据此断言泄漏。idle in transaction 则需要查业务是否在事务中等待外部请求、遗漏提交或回滚。会话存在很久也不自动证明它应该被终止,应结合任务用途判断。
先控制连接来源,再考虑增加服务器上限
如果连接来源已经明确,可以先减少非必要的并发任务、修复重复池或未归还连接,再按总预算调整池大小。降低池上限会增加等待,需要同时观察请求延迟和任务积压。设置取连接等待超时能让失败更可见,但不能替代容量修复。
不要批量终止所有空闲会话。终止正在使用的连接会使请求失败,事务可能回滚;确需处理时,只针对已确认可中断的会话,安排对应业务恢复。连接代理也有事务、会话状态等兼容要求,不能把“加一个池化代理”写成无需评估的替换。
增大 max_connections 会影响资源需求,且该设置通常需要重启数据库。修改前保存配置、确认内存与工作负载预算,安排维护窗口和回退。增大上限后错误消失,只能说明症状缓解,还需要确认连接来源是否已受到控制。
用连接趋势和业务恢复一起验收
修复后观察应用正常负载、后台任务和滚动部署期间的连接总量,确认管理连接仍能建立;再检查原来失败的请求和任务恢复。池内排队数量、等待时间以及长事务是否下降,也应与改动的目标对应。
如果只是在低负载时暂时恢复,保留这项观察,不把它当作高峰问题已解决。连接数量从哪里来、为什么能受预算控制,应该能从实例配置和数据库统计中对应出来。


