PostgreSQL 连接池耗尽排查:pg_stat_activity、idle in transaction 与连接上限
一、先看清“连接耗尽”是哪一层
PostgreSQL 报 FATAL: sorry, too many clients already,或者应用侧一直 connection timeout,通常不是单一原因。连接数至少有四层限制:
- 数据库
max_connections; - 单个应用连接池的
max_size; - 应用实例数量 × 每实例连接数;
- 中间件(PgBouncer、云数据库代理)的连接上限。
很多事故是“扩容后连接数翻倍”导致的:实例从 2 个扩到 10 个,每个池仍然 20 条连接,数据库瞬间多出 160 条空闲连接。排查时先把这四层数字列出来,再决定调哪一层。
二、用 pg_stat_activity 找连接都在干什么
第一条 SQL 是按状态分组:
SELECT state, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state
ORDER BY count(*) DESC;
如果 idle 很多,说明连接池开得比实际并发大;如果 idle in transaction 很多,说明事务开启后没有及时提交或回滚;如果 active 长时间不降,通常是慢查询或锁等待。
继续定位具体会话:
SELECT pid, usename, application_name, client_addr,
state, wait_event_type, wait_event,
now() - xact_start AS xact_age,
now() - query_start AS query_age,
left(query, 80) AS query_head
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY xact_age DESC NULLS LAST
LIMIT 30;
idle in transaction 是最危险的状态:它占着连接、可能持有锁,还会阻止 vacuum 回收。应用里“先 begin,再调用外部接口,最后 commit”的写法最容易制造这种会话。
三、连接池泄漏的几个高频原因
- 异常路径没有归还连接:拿到连接后抛异常,
finally里没有close(); - 事务没有结束:
BEGIN后某个分支直接 return,连接回到池里仍处于事务中; - 用了 session 级设置:
SET search_path、临时表、 advisory lock 没清理,连接被复用时状态污染; - 长连接 + 网络抖动:客户端以为连接还活着,服务端已经断开,池里出现半死连接;
- 连接池配置太小:并发一高就排队,表现为“超时”而不是“连接耗尽”。
排查时给连接池加两个指标:当前借出数、等待队列长度。借出数长期贴近上限、等待队列持续增长,就说明池太小或连接归还太慢,而不是数据库连不上。
四、几个能兜底的参数
-- 空闲事务超过 60 秒自动断开
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60s';
-- 单条语句最长 30 秒
ALTER SYSTEM SET statement_timeout = '30s';
-- PG14+:普通空闲连接超过 10 分钟断开
ALTER SYSTEM SET idle_session_timeout = '10m';
这些参数是“止损”,不是“修复”。真正要改的是事务边界:不要在事务里做 RPC、发消息、等用户输入;批量任务分段提交;连接池借出和归还必须成对出现。
五、PgBouncer 的 transaction 模式要注意什么
transaction 模式下,一条连接只在事务期间绑定给某个客户端,事务结束就归还。它能把连接数压得很低,但有两个代价:
- 会话级状态不能跨事务保留,
SET、临时表、prepared statement 可能失效; - 应用不能依赖
session级的 advisory lock 或游标。
如果业务大量使用 prepared statement,要么开启 PgBouncer 的协议级支持,要么改用 session 模式并接受更高的连接占用。
六、监控与处置顺序
建议至少监控:pg_stat_activity 按状态计数、连接数 / max_connections 使用率、idle in transaction 数量、最老事务年龄。
出现告警时的处置顺序:
- 先看是否某类
idle in transaction暴涨,必要时终止最老的空闲事务; - 再看应用实例数和连接池配置是否匹配;
- 最后才考虑调大
max_connections,因为每条连接都有内存和调度开销,盲目调大可能让问题更糟。
连接耗尽的本质通常是“连接被占着但没干活”。把事务缩短、把归还可视化、把超时配上,比单纯扩容更有效。
- 点赞
- 收藏
- 关注作者
评论(0)