I
Editorial
“FATAL: sorry, too many clients already” 或 “remaining connection slots are reserved” 是 PostgreSQL 线上常见故障。很多人的第一反应是把 max_connections 调大,但这往往只是把问题推后,甚至让性能更差。本帖整理排查思路和长期方案,参数含义以官方文档为准。
一、先看当前连接都在干什么
1. 按状态统计:
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
重点关注 active、idle、idle in transaction 三类。
2. 按应用和来源统计:
SELECT usename, application_name, client_addr, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC;
通常能直接定位是哪个服务把连接占满。
3. 找长时间空闲事务:
SELECT pid, usename, state, now()-xact_start AS xact_age, query FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY xact_age DESC;
idle in transaction 不但占连接,还会阻止 VACUUM 回收死元组、持有锁,危害比普通 idle 大得多。
二、常见根因
1. 应用每个实例都有自己的连接池,实例数扩容后总连接数 = 实例数 × 每实例池大小,很容易超过上限。
2. 代码里开启事务后在事务中做外部调用(HTTP 请求、消息队列),或异常路径没有提交/回滚,导致 idle in transaction。
3. 连接泄漏:借出的连接没有归还,池子最终耗尽,新请求只好新建连接。
4. 慢查询或锁等待使连接长时间 active,并发请求不断堆积。
三、为什么不建议无限调大 max_connections
PostgreSQL 是每连接一个后端进程的模型,每个连接都有内存开销(work_mem 等按操作分配,并发高时总量可观),大量连接还会增加锁管理和快照计算的开销。连接数远高于 CPU 核数时,吞吐通常不升反降。更合理的做法是限制真正并发执行的数量,让其余请求在连接池里排队。
四、短期止血
1. 保留管理连接:superuser_reserved_connections 默认会为超级用户保留少量连接,确保 DBA 能登录处理。
2. 结束问题会话:确认影响后用 pg_terminate_backend(pid) 结束长时间 idle in transaction 的会话。
3. 设置超时兜底:
idle_in_transaction_session_timeout:自动终止空闲事务过久的会话;
statement_timeout:限制单条语句执行时间(建议按角色或应用分别设置,而不是全局一刀切);
较新版本还提供 idle_session_timeout,可清理长期空闲连接,但对使用连接池的应用要谨慎,避免池里的连接被服务端断开后报错。
五、长期方案:连接池分层
1. 应用侧连接池:池大小按“实际需要的并发查询数”设置,而不是按请求并发设置。一个常被引用的起点估算是“核数 × 2 + 有效磁盘数”,再结合压测调整。
2. 数据库侧连接池(如 PgBouncer):transaction 模式下多个客户端连接复用少量服务端连接,适合大量短事务的场景。注意 transaction 模式下会话级特性受限(SET 会话参数、advisory lock、部分预处理语句用法),上线前要确认应用和驱动是否兼容。
3. 按服务设置连接上限:ALTER ROLE app_x CONNECTION LIMIT n; 防止单个服务耗尽全部连接。
六、监控建议
1. 连接数按状态分类的时间曲线,idle in transaction 设单独告警;
2. 最长事务时长告警(比如超过 5 分钟);
3. 连接池侧的等待队列长度和等待时间,排队变长往往比数据库报错更早出现。
欢迎分享你们的连接池配置和踩过的坑。
一、先看当前连接都在干什么
1. 按状态统计:
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
重点关注 active、idle、idle in transaction 三类。
2. 按应用和来源统计:
SELECT usename, application_name, client_addr, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC;
通常能直接定位是哪个服务把连接占满。
3. 找长时间空闲事务:
SELECT pid, usename, state, now()-xact_start AS xact_age, query FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY xact_age DESC;
idle in transaction 不但占连接,还会阻止 VACUUM 回收死元组、持有锁,危害比普通 idle 大得多。
二、常见根因
1. 应用每个实例都有自己的连接池,实例数扩容后总连接数 = 实例数 × 每实例池大小,很容易超过上限。
2. 代码里开启事务后在事务中做外部调用(HTTP 请求、消息队列),或异常路径没有提交/回滚,导致 idle in transaction。
3. 连接泄漏:借出的连接没有归还,池子最终耗尽,新请求只好新建连接。
4. 慢查询或锁等待使连接长时间 active,并发请求不断堆积。
三、为什么不建议无限调大 max_connections
PostgreSQL 是每连接一个后端进程的模型,每个连接都有内存开销(work_mem 等按操作分配,并发高时总量可观),大量连接还会增加锁管理和快照计算的开销。连接数远高于 CPU 核数时,吞吐通常不升反降。更合理的做法是限制真正并发执行的数量,让其余请求在连接池里排队。
四、短期止血
1. 保留管理连接:superuser_reserved_connections 默认会为超级用户保留少量连接,确保 DBA 能登录处理。
2. 结束问题会话:确认影响后用 pg_terminate_backend(pid) 结束长时间 idle in transaction 的会话。
3. 设置超时兜底:
idle_in_transaction_session_timeout:自动终止空闲事务过久的会话;
statement_timeout:限制单条语句执行时间(建议按角色或应用分别设置,而不是全局一刀切);
较新版本还提供 idle_session_timeout,可清理长期空闲连接,但对使用连接池的应用要谨慎,避免池里的连接被服务端断开后报错。
五、长期方案:连接池分层
1. 应用侧连接池:池大小按“实际需要的并发查询数”设置,而不是按请求并发设置。一个常被引用的起点估算是“核数 × 2 + 有效磁盘数”,再结合压测调整。
2. 数据库侧连接池(如 PgBouncer):transaction 模式下多个客户端连接复用少量服务端连接,适合大量短事务的场景。注意 transaction 模式下会话级特性受限(SET 会话参数、advisory lock、部分预处理语句用法),上线前要确认应用和驱动是否兼容。
3. 按服务设置连接上限:ALTER ROLE app_x CONNECTION LIMIT n; 防止单个服务耗尽全部连接。
六、监控建议
1. 连接数按状态分类的时间曲线,idle in transaction 设单独告警;
2. 最长事务时长告警(比如超过 5 分钟);
3. 连接池侧的等待队列长度和等待时间,排队变长往往比数据库报错更早出现。
欢迎分享你们的连接池配置和踩过的坑。