PostgreSQL 连接耗尽:先分清会话占用、连接池放大与长事务

16 次浏览2 条回复

适用于 PostgreSQL 13+、psql 13+。以下命令需要通过现有安全路径连接数据库;完整查看其他会话详情通常需要超级用户、pg_monitorpg_read_all_stats 权限。示例不包含终止会话或修改配置,先保留故障时间线。

1. 确认是连接槽位紧张,而不是服务未启动

SELECT now() AT TIME ZONE 'UTC' AS utc_time, version();

SELECT current_setting('max_connections')::int AS max_connections,
       current_setting('superuser_reserved_connections')::int AS superuser_reserved,
       count(*) FILTER (WHERE backend_type = 'client backend') AS client_backends
FROM pg_stat_activity;

SELECT backend_type, count(*)
FROM pg_stat_activity
GROUP BY backend_type
ORDER BY count(*) DESC;

client_backends 可作为应用会话占用的直接指标,但不要把它简单当成所有槽位的精确余额;复制连接和不同后端类型应结合第二条查询一起看。客户端出现 too many clients already 或“剩余连接槽位保留给管理连接”时,再与同一 UTC 时间的 PostgreSQL 日志对应。pg_isready 只适合判断服务器是否响应连接探测,不能证明普通应用角色还能建立并完成查询。

2. 找出连接集中在哪个来源

SELECT datname, usename, application_name, client_addr, state, count(*) AS sessions
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY datname, usename, application_name, client_addr, state
ORDER BY sessions DESC
LIMIT 30;

SELECT pid, datname, usename, application_name, client_addr, state,
       now() - xact_start AS xact_age,
       now() - state_change AS state_age,
       wait_event_type, wait_event
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY xact_start NULLS LAST
LIMIT 30;

重点区分三类现象:大量 active 可能是查询变慢导致连接归还不及时;大量 idle 常见于每个进程各自建立过大的池;长时间 idle in transaction 可能同时占连接、持有事务快照或锁。application_name 为空时,应回到连接串和驱动配置补齐标识,否则很难区分实例。查询文本、地址和角色名可能包含敏感信息,公开排障时只保留必要摘要。

3. 对齐数据库日志与慢点

先确认日志去向和生效配置:

SHOW log_destination;
SHOW logging_collector;
SHOW log_directory;
SHOW log_connections;
SHOW log_disconnections;

SELECT name, setting, unit, source, sourcefile, pending_restart
FROM pg_settings
WHERE name IN (
  'max_connections',
  'superuser_reserved_connections',
  'statement_timeout',
  'idle_in_transaction_session_timeout'
)
ORDER BY name;

若由 systemd 托管,可从实际单元读取同一窗口;发行版的单元名可能不是 postgresql,执行前先确认:

systemctl list-units --type=service 'postgresql*'
sudo journalctl -u <actual-postgresql-unit> \
  --since '<UTC-start>' --until '<UTC-end>' --no-pager -o short-iso

把首次拒绝连接的时间与部署、流量变化、查询延迟、锁等待和应用副本数对应。只看到连接数高并不能判断是容量不足还是下游变慢造成的堆积。

4. 核算连接池的总预算

按“每个应用进程的最大池大小 × 进程数 × 副本数”计算理论上限,并把后台任务、迁移工具、监控、复制和管理连接单独列出。常见问题不是单个池很大,而是水平扩容后每个副本都复制了同一池配置。

修复优先级通常是:先让连接池具备等待上限和背压,再缩小每副本池并修复连接泄漏或慢查询;只有完成内存与并发评估后,才考虑提高 max_connections。该参数需要重启才能生效,pg_reload_conf() 不能代替重启;可用上面的 pending_restart 验证。不要在没有确认事务用途时批量终止 idle in transaction 会话。

5. 闭环验证

变更后至少确认:

SHOW max_connections;
SELECT state, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state
ORDER BY count(*) DESC;

在超过原故障周期的观察窗口内,应用错误日志不再新增连接获取超时,客户端会话数稳定低于预算,长事务和 idle in transaction 没有持续累积,并通过真实入口完成无副作用查询。若做了应用扩缩容,还要重新按新副本数核算池上限;只在低流量时复测一次不足以证明问题已经闭环。

再补一个代理层观察点:链路中有 PgBouncer 时,pg_stat_activity 看到的是它持有的服务端连接,不是等待中的全部应用客户端。数据库侧连接数稳定,也可能已经在池前排队。先通过已授权的 PgBouncer 管理入口执行只读命令(输出列会随版本变化,因此先记录版本):

SHOW VERSION;
SHOW POOLS;
SHOW STATS;
SHOW CONFIG;

重点对照 SHOW POOLS 中的 cl_waitingsv_activesv_idlemaxwait。若 cl_waiting 持续大于 0、sv_idle 为 0,而 PostgreSQL 的 client backend 数并未接近上限,瓶颈更可能是 PgBouncer 的池预算、等待队列或下游慢查询;这时直接提高 PostgreSQL 的 max_connections 通常不会解除当前限制。

还要记录 pool_mode:在 transaction 模式下,应用连接与 PostgreSQL 会话不是一一对应,数据库侧的 idle 不能直接推断为应用泄漏。闭环时应同时从真实应用入口做无副作用请求,并在同一 UTC 窗口确认 cl_waiting 回落、maxwait 不再增长、数据库错误日志没有新增拒绝连接。管理控制台可能暴露数据库名、用户名和地址,公开结果前应做必要脱敏。

还应排除角色级和数据库级上限。即使全局 max_connections 尚有余量,应用也可能因 rolconnlimitdatconnlimit 被单独拒绝;先按客户端原始错误区分是否出现 too many connections for roletoo many connections for database。适用于 PostgreSQL 13+,查看所有角色、数据库和其他会话通常需要管理员权限或相应监控权限。

SELECT rolname, rolcanlogin, rolconnlimit
FROM pg_roles
WHERE rolcanlogin
ORDER BY rolname;

SELECT datname, datallowconn, datconnlimit
FROM pg_database
ORDER BY datname;

SELECT usename, datname, count(*) AS client_backends
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY usename, datname
ORDER BY client_backends DESC;

rolconnlimitdatconnlimit-1 表示不限制。第三条查询用于找出当前占用,但连接限制的检查并非严格锁定计数,并发建立连接时可能有短暂偏差,因此要和同一 UTC 窗口的服务端日志、应用连接池指标一起判断。也不要只看当前值:短连接风暴可能在采样前已经回落。

如果需要调整,应先核算该角色或数据库下所有副本、任务进程和运维工具的连接预算,再用 ALTER ROLE ... CONNECTION LIMIT ...ALTER DATABASE ... CONNECTION LIMIT ...;这类修改会影响后续连接,不会清理既有会话。验证时应使用与应用相同的普通登录角色、目标数据库和连接入口执行 SELECT 1,并持续观察超过原故障周期,确认连接获取错误不再新增且会话峰值低于新预算。不要用管理员连接成功代替应用身份验证。