Skip to content

连接管理 ​

PostgreSQL 采用「每连接一进程」的模型:客户端每建立一条连接,postmaster 就 fork 出一个独立的后端进程(backend)为其服务。这个模型带来了极强的隔离性——一个后端崩溃不会拖垮其他连接——但也让「连接」在 PostgreSQL 里成为一种相对昂贵的资源:每条连接背后是一个真实的操作系统进程,携带一份独立的内存上下文、独立的缓存与独立的锁管理参与者身份。当连接数从几十涨到几千时,内存开销、进程调度开销与锁表竞争都会同步放大,这就是日常运维中「连接管理」之所以成为第一课的原因。

连接管理说到底要回答三个问题:允许多少人连(max_connections 与各类预留连接参数)、现在连接健康吗(pg_stat_activity 的状态分析、空闲事务与各类超时参数)、连接数打满了怎么办(应急定位与 terminate、以及长期意义上的池化改造)。这三个问题分别对应容量规划、健康监控与故障处置,构成日常运维中最常打交道的操作面。

本页是阶段十七「监控与运维」中「日常运维」的第一节。我们会先从连接的生命周期讲起,把 postmaster → backend 的进程模型与内存结构理清楚;再逐一剖析 max_connections、superuser_reserved_connections、reserved_connections(PostgreSQL 16 引入)这三个容量参数,以及它们与连接池 max_client_conn 的关系;然后深入 pg_stat_activity 的 state 分布分析与空闲事务治理;最后用一场「连接数打满」的应急演练把所有知识点串起来。

前置知识

建议先阅读:云监控与告警接入

基础概念 ​

要管好连接,先要知道一条连接从建立到销毁经历了什么。PostgreSQL 的连接生命周期可以粗略分为五个阶段:

text
  客户端                    postmaster                    backend 进程
    │                          │                              │
    │ ── TCP 三次握手 ────────► │                              │
    │ ── 启动包(startup) ─────► │ 读取 startup message         │
    │                          │ fork() ────────────────────► │
    │ ◄──── 认证握手 ──────────┼─────────────────────────────►│ md5/scram/gss...
    │ ◄──── 参数状态报文 ──────┼─────────────────────────────►│ 发送 server_version 等
    │ ── 简单/扩展查询 ───────► │(postmaster 不再介入)──────►│ 解析、优化、执行
    │ ◄──── 结果集 ────────────┼─────────────────────────────►│
    │ ── Terminate / 断连 ────► │ reap 子进程,回收槽位         │
    │                          │                              │
阶段发生的事情运维关注点
1. 建立连接TCP/Unix socket 三次握手,进入 postmaster 监听队列listen_addresses、port、tcp_backlog(内核参数)
2. fork 后端postmaster fork 出 backend 子进程fork 成本(约 ms 级),短连接风暴的元凶
3. 认证与会话建立依据 pg_hba.conf 完成认证,backend 发送运行参数认证方式(SCRAM)、pg_hba.conf 匹配顺序
4. 服务期backend 处理查询,维护事务与内存上下文pg_stat_activity 的 state、内存占用
5. 终止客户端退出、异常断开或被 terminatepg_stat_activity 残留、TCP 半开连接

后端进程模型 ​

「每连接一进程」是理解 PostgreSQL 连接成本的核心。每个 backend 进程的构成:

  • 一个独立的进程:不是线程。进程意味着独立的地址空间、独立的调度实体,OS 层面的切换成本高于线程模型,但崩溃隔离性极好。
  • 一份私有内存:work_mem、hash_mem_multiplier 等参数控制的是「单操作」上限,而每个 backend 都有固定的一块基础内存(维护缓存、锁引用、事务状态等),通常在几 MB 到十几 MB 量级。
  • 共享内存的映射:backend 读写 shared buffers、WAL buffers、锁表(shared memory),多进程之间通过 LWLock 竞争这些结构。
  • 一张锁表里的席位:每个 backend 都在锁管理器中占据空间,max_locks_per_transaction × max_connections 决定了共享锁表的总容量。这意味着调大 max_connections 也会推高共享内存占用。
text
                    ┌──────────────── shared memory ────────────────┐
                    │  shared buffers │ WAL buffers │ 锁表 │ ...    │
                    └──────▲──────▲──────────▲──────────▲───────────┘
                           │      │          │          │
   客户端 A ─── backend1 ──┘      │          │          │
   客户端 B ─── backend2 ─────────┘          │          │
   备库 walreceiver ── walsender ────────────┘          │
   psql -c "VACUUM" ─── autovacuum worker ──────────────┘

这张图还想强调一件事:pg_stat_activity 里看到的连接不只是业务连接。walsender、autovacuum worker、后台并行 worker、逻辑复制的 apply 相关进程都会出现在里面,它们同样受 max_connections 约束(walsender 占用名额,autovacuum worker 不占业务连接但占进程槽位)。

连接相关的容量参数族 ​

PostgreSQL 用一组参数把「总共允许多少连接」切成几个配额区间,理解它们的关系是容量规划的基本功:

参数默认值引入版本作用
max_connections100早期全实例最大并发连接数上限
superuser_reserved_connections3早期仅为超级用户(及拥有 pg_use_reserved_connections 角色的成员,见下)保留的连接名额
reserved_connections0PostgreSQL 16仅为拥有 pg_use_reserved_connections 角色成员身份的非超级用户保留的名额

三者的关系是一条「水位线」:普通用户最多能把连接打到 max_connections − superuser_reserved_connections − reserved_connections;在此基础上,预留配额内的连接可以被超级用户/预留角色使用。这样设计的目的只有一个——业务连接打满时,运维人员还能连进来救火。

为什么要连接池 ​

既然每连接一进程很贵,那么答案自然是让进程复用:连接池(PgBouncer 等)在前端维持少量到 PostgreSQL 的真实连接,把成百上千的应用连接多路复用上去。PostgreSQL 官方文档对 max_connections 的建议值非常克制——对于连接池加持的部署,几百以内是常见上限。与 PgBouncer 的 max_client_conn(池子允许的前端连接数)配合,可以做到「应用侧几百连接无感,数据库侧只有几十个真实后端」。

TIP

PgBouncer 的部署、三种池化模式(session / transaction / statement)与 max_client_conn 的配置细节,请阅读 PgBouncer 连接池。本页只从「数据库侧参数如何与池配合」的角度展开。

语法与用法 ​

连接管理的「语法」散落在三类地方:配置参数(postgresql.conf / ALTER SYSTEM)、监控视图(pg_stat_activity)与管理函数(pg_terminate_backend 等)。下面按运维动线把它们过一遍。

查看当前容量参数 ​

sql
-- 三大容量参数的当前值
SELECT name, setting, unit, source, pending_restart
FROM pg_settings
WHERE name IN ('max_connections',
               'superuser_reserved_connections',
               'reserved_connections',
               'idle_in_transaction_session_timeout',
               'idle_session_timeout',
               'client_connection_check_interval');
text
                 name                  | setting | unit |      source       | pending_restart
---------------------------------------+---------+------+-------------------+-----------------
 client_connection_check_interval      | 0       | ms   | default           | f
 idle_in_transaction_session_timeout   | 0       | ms   | configuration file| f
 idle_session_timeout                  | 0       | ms   | configuration file| f
 max_connections                       | 100     |      | configuration file| f
 reserved_connections                  | 0       |      | default           | f
 superuser_reserved_connections        | 3       |      | default           | f
(6 rows)

注意 pending_restart 列:max_connections 等进程级参数修改后需要重启才生效,这一列会显示 t,是变更后验证「是否已等待重启」的快捷方式。

查看连接分布 ​

sql
-- 按数据库与状态统计当前连接
SELECT datname, state, count(*) AS cnt
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY datname, state
ORDER BY datname, cnt DESC;
text
 datname  |         state         | cnt
----------+-----------------------+-----
 app      | active                |   3
 app      | idle                  |  86
 app      | idle in transaction   |   2
 postgres | active                |   1
(4 rows)
sql
-- 看连接的「来源画像」:谁、从哪台机器、用什么程序连的
SELECT usename,
       application_name,
       client_addr,
       state,
       count(*) AS cnt
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY usename, application_name, client_addr, state
ORDER BY cnt DESC;

修改参数:ALTER SYSTEM ​

max_connections 修改后需要重启;各类超时参数支持 reload 即可生效,推荐用 ALTER SYSTEM 统一管理:

sql
-- 需要 reload 的参数:改完执行 pg_reload_conf() 或 systemctl reload postgresql
ALTER SYSTEM SET idle_in_transaction_session_timeout = '10min';
ALTER SYSTEM SET idle_session_timeout = '30min';
ALTER SYSTEM SET client_connection_check_interval = '60s';

SELECT pg_reload_conf();

-- 需要重启的参数
ALTER SYSTEM SET max_connections = 200;
ALTER SYSTEM SET superuser_reserved_connections = 5;
ALTER SYSTEM SET reserved_connections = 3;
-- 之后择机重启实例

终止连接 ​

sql
-- 温和:取消当前正在执行的查询(会话保留)
SELECT pg_cancel_backend(pid);

-- 强硬:终止整个会话
SELECT pg_terminate_backend(pid);

两个函数都接受 pid(来自 pg_stat_activity.pid),并返回布尔值。pg_cancel_backend 相当于给正在执行的查询发一个「中断」,事务回滚但连接保留;pg_terminate_backend 则直接干掉后端进程。

详细说明 ​

max_connections 与内存预算 ​

很多人把 max_connections 当成「性能参数」——连接数调大就能扛更多流量。事实恰恰相反:max_connections 是一个资源预算参数,它同时消耗三类资源:

  1. 每后端的私有内存。即使每个 backend 只做最简单的事,基础内存也在 MB 量级;如果查询复杂,work_mem 的用量还会叠加。粗略估算:max_connections × (几 MB 基础内存 + 潜在 work_mem × 并发算子数)。
  2. 共享内存中的锁表。锁表容量为 max_locks_per_transaction × (max_connections + max_prepared_transactions + ...),max_locks_per_transaction 默认 64。连接数翻倍,锁表预算也翻倍。
  3. 进程调度与上下文切换。几百个活跃进程同时争 CPU,内核调度开销会肉眼可见地反映在延迟抖动上。

这就是为什么生产环境的推荐路径永远是:宁可 max_connections 保守(例如 100~300),把接入压力交给连接池,而不是把它调到几千。此外,max_connections 的修改需要重启实例,配合主备切换可以实现「滚动重启」,这属于变更管理范畴。

三个预留参数的水位关系 ​

把三个预留参数放进一张图会更直观:

text
 0 ────────────────────────────────────────────────────── max_connections
 │◄────────────── 普通用户可用的连接区间 ──────────────►│
 │                                                      │◄─ 超级用户专属 ─┐
 │◄────────── 普通用户区间 ──────────►│◄─ reserved ──►│                  │
 │                                    │ (PG16+)       │◄─ superuser ────┘
 ▲                                    ▲               ▲
 │                                    │               │
 max_connections                     减去 reserved_connections
 − superuser_reserved_connections    减去 superuser_reserved_connections
 − reserved_connections              就是普通用户的上限
  • superuser_reserved_connections(默认 3):从 max_connections 的尾部切出一段,只有超级用户(以及 PG16+ 中拥有 pg_use_reserved_connections 角色的成员)能占用。业务连接打满时,postgres 用户依然能登入——这是应急通道,不要把 3 改成 0,也建议随着运维工具(监控采集器、备份 agent 等)的数量适度上调到 5~10。
  • reserved_connections(PostgreSQL 16 引入,默认 0):位于 superuser 区间之前的另一段预留。它服务的对象是「非超级用户,但需要保证有连接可用」的场景,典型如监控采集账号、failover 工具账号。授予方式是把目标角色加进 pg_use_reserved_connections:
sql
-- 让监控账号能使用预留连接区间
GRANT pg_use_reserved_connections TO monitor_user;
  • 优先级顺序:普通用户区间最先被消耗;其次是 reserved_connections 区间;最后是 superuser_reserved_connections 区间。三个区间加起来才是 max_connections。

版本提示

reserved_connections 与 pg_use_reserved_connections 角色是 PostgreSQL 16 新增的能力。在更早的版本上,只有 superuser_reserved_connections 一段预留可用;监控等非超级用户账号只能靠「控制其并发」来保护,或将其升级为拥有适当权限的角色。

pg_stat_activity 的 state 分布 ​

pg_stat_activity 是连接分析的中央视图。运维视角下最重要的列是 state,它描述了每个 backend 此刻在做什么:

state 取值含义健康与否
active正在执行 SQL正常,但数量高说明并发压力大
idle空闲,等待客户端下发新命令正常(连接池场景的常态),数量过高说明池参数不合理
idle in transaction已开启事务但当前无 SQL 在执行危险,事务悬挂
idle in transaction (aborted)事务内语句报错后未回滚危险,同上
fastpath function call正在执行 fast-path 函数调用罕见
disabledtrack_activities 关闭时的占位配置问题

日常巡检只需要一条「state 分布」SQL(见上文语法部分),但要看懂它,还需要配合三个时间戳列:

  • backend_start:连接建立时间 → 判断连接的「年龄」。
  • xact_start:当前事务开始时间 → 长事务的判定依据(为 NULL 表示当前无事务)。
  • state_change:状态最后一次变化的时间 → idle 连接挂了多久、idle in transaction 挂了多久,都用 now() − state_change 计算。

一套常用的「分层分析」查询:

sql
-- 1) 全局体检:各 state 的数量与平均闲置时长
SELECT state,
       count(*)                                   AS cnt,
       round(avg(extract(epoch FROM now() - state_change))::numeric, 1) AS avg_idle_s,
       max(extract(epoch FROM now() - state_change))::int               AS max_idle_s
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state;

-- 2) idle 连接按客户端聚合:找出连接泄漏的应用
SELECT client_addr, usename, application_name, count(*) AS idle_cnt
FROM pg_stat_activity
WHERE state = 'idle'
  AND backend_type = 'client backend'
GROUP BY client_addr, usename, application_name
HAVING count(*) > 20
ORDER BY idle_cnt DESC;

-- 3) 连接年龄分布:看是否有永不释放的长连接
SELECT width_bucket(extract(epoch FROM now() - backend_start),
                    0, 86400, 24)                        AS hour_bucket,
       count(*)                                          AS cnt
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY hour_bucket
ORDER BY hour_bucket;

空闲事务的危害 ​

idle in transaction(空闲事务)是连接管理中最危险的状态,没有之一。它指的是:客户端发出了 BEGIN(或隐式开启了事务),执行了若干语句,然后拿着事务不提交也不回滚,自己不动了——可能是应用死锁在等别的服务、可能是代码漏了 commit、也可能是批处理任务卡住。

空闲事务的连锁反应:

  1. 阻塞 VACUUM 前进:事务的快照会钉住所有比它新的死元组,autovacuum 无法回收这些表的空间,膨胀随之而来。一个挂了一小时的空闲事务,足以让一张高频更新的表膨胀出可观的死元组。
  2. 持有锁不放:事务期间获取的行锁、表锁全部保持,其他会话的 DDL、更新会被阻塞,pg_locks 里出现长长的等待队列。
  3. 占用一个连接名额:它本身就是一条活连接,叠加连接数压力。
  4. 阻止 XID 回卷:长时间运行的事务还会阻止 WAL 的清理与 XID 冻结推进(严重时触发 XID 耗尽告警)。

治理空闲事务的超时参数族 ​

PostgreSQL 为「会话/事务空闲」提供了一组防御性超时,合理配置后可以自动清理失控会话:

参数引入版本默认值作用对象建议起点
idle_in_transaction_session_timeout9.60(不限)事务处于 idle in transaction 状态的时长1~15 分钟
idle_session_timeout140(不限)纯 idle(无事务)会话的时长10~60 分钟,谨慎启用
client_connection_check_interval140(不限)长查询执行期间周期性检测客户端 TCP 是否已断开30~120 秒
tcp_keepalives_idle早期0(用系统默认,Linux 常为 7200s)TCP 保活探测前的空闲秒数300 秒
tcp_keepalives_interval早期0保活探测重试间隔30 秒
tcp_keepalives_count早期0判定连接死亡的重试次数5 次

几点关键差异:

  • idle_in_transaction_session_timeout 是最安全、最应该开的一个。触发时 PostgreSQL 主动回滚事务并断开连接,日志中会记录 FATAL: terminating connection due to idle-in-transaction timeout。它对「漏 commit」的应用是天然的兜底。
  • idle_session_timeout 要谨慎:很多 legitimate 场景就是「用户开着 psql 去吃饭了」,以及某些连接池会把空闲连接静置在数据库侧——如果池子与数据库各自设了超时,可能出现「池子以为连着、数据库已踢掉」的窗口。PgBouncer 侧已有 server_idle_timeout 时,数据库侧这个参数通常保持 0。
  • client_connection_check_interval(PG14+)解决的是另一个经典问题:客户端断网/被杀后,正在跑几小时的大查询的 backend 并不会立即感知(它根本不看 socket),会继续消耗资源直至查询结束。该参数让 backend 在长查询执行期间每 N 毫秒检查一次 socket 是否可写,若客户端已消失则自动取消查询。
  • tcp_keepalives_* 三件套作用于「更底层的半开连接」:客户端机器突然断电、网线拔掉时,TCP 连接不会立刻断开,靠内核保活探测才能回收。Linux 默认 7200 秒才发起第一次探测,对数据库来说太迟钝,通常会缩短。设为 0 表示沿用操作系统的 sysctl 配置。
conf
# postgresql.conf 中的推荐写法(示例值,按业务调整)
idle_in_transaction_session_timeout = 10min
# idle_session_timeout = 30min          # 按需开启,与连接池超时策略协调
client_connection_check_interval = 60s  # PG14+
tcp_keepalives_idle = 300
tcp_keepalives_interval = 30
tcp_keepalives_count = 5

ALTER SYSTEM 与参数变更流程 ​

ALTER SYSTEM 把参数写入 postgresql.auto.conf(优先级高于 postgresql.conf),是实现「可追溯参数变更」的标准方式。连接相关的变更流程建议固定为:

bash
# 1. 变更前留底
psql -Atc "SELECT name || '=' || setting FROM pg_settings
           WHERE name LIKE '%timeout%' OR name LIKE 'max%connection%';" \
> settings_before_$(date +%F).txt

# 2. 用 ALTER SYSTEM 修改(需要超级用户或 pg_alter_system 角色成员)
psql -c "ALTER SYSTEM SET idle_in_transaction_session_timeout = '10min';"

# 3. reload 并验证
psql -c "SELECT pg_reload_conf();"
psql -c "SHOW idle_in_transaction_session_timeout;"

# 4. 确认 auto.conf 内容(纳入版本管理的应该是变更记录而非该文件本身)
cat $PGDATA/postgresql.auto.conf

两点提醒:其一,ALTER SYSTEM 不能修改 max_connections 这类 postmaster 启动参数的生效方式——它照样只能写文件,需要重启才生效,pg_settings.pending_restart 为 t 就是在提醒你;其二,postgresql.auto.conf 中若出现了错误的设置导致实例无法启动,可以用 ALTER SYSTEM RESET ALL 无法执行时手工编辑该文件删除对应行来恢复。

与连接池的参数协同 ​

数据库侧参数与 PgBouncer 侧参数需要成对设计,否则会出现「互相打架」:

数据库侧池侧(PgBouncer)协同要点
max_connectionsdefault_pool_size × 池内 database/user 组合数真实后端连接总和必须小于 max_connections 并留出预留区间
应用并发接入量max_client_conn前端连接上限,通常远大于真实后端连接数
idle_session_timeoutserver_idle_timeout二选一主导,避免两边同时踢连接造成抖动
idle_in_transaction_session_timeoutserver_reset_query / 池模式transaction 模式下漏 commit 的会话归还时会报错,数据库侧超时兜底
认证方式(SCRAM)auth_type = scram-sha-256两端保持一致,避免降级为 md5

在 transaction pooling 模式下还有一个必须意识到的语义变化:SET、prepared statement(部分场景)、session 级 GUC 都不能跨事务存活。数据库侧的会话级超时参数同样语义受限——这正是为什么在池化架构下,超时治理要尽量用事务级语义的参数(如 idle_in_transaction_session_timeout),或在应用侧显式设置。

连接数告警阈值 ​

连接监控的核心不是「采集」,而是「阈值设计」。推荐的双阈值 + 趋势告警:

指标预警(warning)严重(critical)说明
业务连接数 / max_connections70%85%用 pg_stat_activity 中 client backend 计数
idle in transaction 数量≥ 2 且持续 5 分钟≥ 10每一个都可能是锁源
idle in transaction 最长时长> 5 分钟> 15 分钟与超时参数值对齐
idle 连接占比> 80%> 95%说明池参数失配或应用泄漏
单一 client_addr 连接数突增 2 倍触及配额应用泄漏 / 重连风暴
等待连接的报错日志出现 too many clients already频次 > 1/min已经在丢请求

「too many clients already」这个报错值得单独说明:当普通用户把连接打到预留水位线以下时,PostgreSQL 直接拒绝新连接并抛出该错误。看到它时第一动作不是调大 max_connections,而是先看 pg_stat_activity 找出谁占了连接。

实战示例 ​

示例一:连接数打满的应急处理 ​

凌晨收到告警:应用大面积报错 FATAL: sorry, too many clients already。以下是标准处置序列。

第一步:用预留通道连进来,确认现场。务必用超级用户或预留角色连接,普通用户此刻大概率登不进去:

bash
psql -U postgres -d postgres -c "
SELECT count(*) AS total,
       count(*) FILTER (WHERE state = 'active')                       AS active,
       count(*) FILTER (WHERE state = 'idle')                         AS idle,
       count(*) FILTER (WHERE state = 'idle in transaction')          AS idle_in_tx
FROM pg_stat_activity
WHERE backend_type = 'client backend';"
text
 total | active | idle | idle_in_tx
-------+--------+------+------------
   200 |      6 |  192 |          2
(1 row)

第二步:给 idle 连接画像,找出泄漏来源:

sql
SELECT client_addr,
       usename,
       application_name,
       count(*)                              AS cnt,
       max(now() - state_change)             AS longest_idle
FROM pg_stat_activity
WHERE state = 'idle'
  AND backend_type = 'client backend'
GROUP BY client_addr, usename, application_name
ORDER BY cnt DESC
LIMIT 10;
text
  client_addr   | usename | application_name | cnt | longest_idle
----------------+---------+------------------+-----+--------------
 10.0.8.21      | app     | order-service    |  97 | 03:12:44
 10.0.8.22      | app     | order-service    |  95 | 03:12:41
(2 rows)

画像显示:order-service 两台机器各自挂着约 95 条 idle 连接,最长已闲置 3 小时——典型的应用侧连接池失配或连接泄漏。

第三步:分批 terminate idle 连接。先拿几条验证应用会自动重连,再放量;每条 terminate 都要留下 pid 备查:

sql
-- 先小批量试 10 条
WITH victims AS (
    SELECT pid
    FROM pg_stat_activity
    WHERE state = 'idle'
      AND application_name = 'order-service'
      AND backend_type = 'client backend'
    ORDER BY state_change
    LIMIT 10
)
SELECT pg_terminate_backend(pid), pid FROM victims;
text
 pg_terminate_backend |  pid
----------------------+-------
 t                    | 48311
 t                    | 48314
 t                    | 48320
 t                    | 48322
 t                    | 48329
 t                    | 48331
 t                    | 48335
 t                    | 48340
 t                    | 48342
 t                    | 48347
(10 rows)

观察一分钟:应用恢复正常、too many clients 报错消失,则继续把该应用的其余 idle 连接清掉(把 LIMIT 10 放大或去掉,可加 AND now() - state_change > interval '10 minutes' 只清理真正闲置的)。

第四步:单独处理 idle in transaction。它们不能无脑全杀——先看它拿着什么锁、跑的是什么:

sql
SELECT pid,
       usename,
       now() - xact_start                     AS xact_age,
       now() - state_change                   AS idle_age,
       left(query, 60)                        AS last_query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY xact_start;
text
  pid  | xact_age | idle_age |          last_query
-------+----------+----------+------------------------------
 51007 | 00:41:12 | 00:41:10 | UPDATE public.orders SET ...
(1 row)

确认是失控事务(41 分钟未提交、最后一句是 UPDATE)后,先 pg_cancel_backend 尝试取消,再视情况 pg_terminate_backend;如果该事务持有大量锁并阻塞了其他会话,直接 terminate 并让应用侧重放业务。

第五步(事后):给该应用接入连接池或调整池参数,并为数据库配置兜底超时(见示例三)。

示例二:找出持有锁的空闲事务 ​

空闲事务真正的杀伤力在于锁。当有会话报「等待锁」时,用这条查询直接定位阻塞源头:

sql
-- 谁在等待、被谁阻塞
SELECT blocked.pid                    AS blocked_pid,
       blocked.usename                AS blocked_user,
       left(blocked.query, 40)        AS blocked_query,
       blocking.pid                   AS blocking_pid,
       blocking.state                 AS blocking_state,
       now() - blocking.xact_start    AS blocking_xact_age
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
  ON blocking.pid = ANY (pg_blocking_pids(blocked.pid));
text
 blocked_pid | blocked_user |         blocked_query          | blocking_pid |     blocking_state      | blocking_xact_age
-------------+--------------+--------------------------------+--------------+-------------------------+-------------------
       52480 | app          | UPDATE public.stock SET q...   |        51007 | idle in transaction     | 00:41:26
(1 row)

pg_blocking_pids() 一次给出阻塞者的 pid 列表(包括行锁、表锁、缓冲区锁等所有等待类型),比手工关联 pg_locks 高效得多。处理方式与示例一第四步相同:确认无害后 terminate 阻塞者。

示例三:超时兜底与验证 ​

应急处理后,落一套防复发配置:

sql
ALTER SYSTEM SET idle_in_transaction_session_timeout = '10min';
ALTER SYSTEM SET client_connection_check_interval = '60s';
ALTER SYSTEM SET tcp_keepalives_idle = 300;
ALTER SYSTEM SET tcp_keepalives_interval = 30;
ALTER SYSTEM SET tcp_keepalives_count = 5;
SELECT pg_reload_conf();

-- 验证生效
SELECT name, setting FROM pg_settings
WHERE name IN ('idle_in_transaction_session_timeout',
               'client_connection_check_interval',
               'tcp_keepalives_idle');
text
                 name                  | setting
---------------------------------------+---------
 client_connection_check_interval      | 60000
 idle_in_transaction_session_timeout   | 600000
 tcp_keepalives_idle                   | 300
(3 rows)

做个小实验验证超时确实在工作:

sql
-- 会话 A
BEGIN;
UPDATE public.orders SET note = 'hold' WHERE id = 1;
-- 然后什么都不做,等 10 分钟
text
FATAL:  terminating connection due to idle-in-transaction timeout
服务器意外地关闭了连接

日志中也会留下对应记录,可纳入告警扫描:

text
2026-09-30 03:41:12 CST [51007] app@orders FATAL:  terminating connection due to idle-in-transaction timeout
2026-09-30 03:41:12 CST [51007] app@orders CONTEXT:  UPDATE public.orders

示例四:为不同角色设置差异化超时 ​

超时参数既可以在实例级配置,也可以按角色覆盖。例如给报表账号更宽松的空闲事务窗口,给普通业务账号更严格的限制:

sql
-- 实例级兜底(严格)
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
SELECT pg_reload_conf();

-- 报表账号放宽到 30 分钟
ALTER ROLE report_user SET idle_in_transaction_session_timeout = '30min';

-- 某个已知会跑大事务的迁移账号再放宽
ALTER ROLE migrator SET idle_in_transaction_session_timeout = '2h';

角色的设置在连接建立时加载,覆盖实例级默认值。这种「实例级收紧 + 角色级豁免」的模式比一刀切更不容易误伤。

注意事项 ​

max_connections 是重启参数,不要指望在线救火时调大它。 收到 too many clients already 报错时,改配置救不了眼前——必须重启才生效。应急的正确顺序永远是:预留通道进入 → 分析 pg_stat_activity → terminate 泄漏连接 → 事后做池化或阈值整改。

不要把 superuser_reserved_connections 调成 0。 预留区间是打满时的唯一登入通道。如果监控采集、备份 agent 等以非超级用户运行,在 PG16+ 用 reserved_connections + pg_use_reserved_connections 为它们留位,避免它们与业务抢名额,也避免它们把超级用户区间吃满导致真超级用户进不来。

terminate 是止血手段,不是治理手段。 pg_terminate_backend 会让客户端收到连接断开错误,长事务回滚。如果应用没有重连与重试逻辑,可能把一个连接问题放大成业务故障。放量 terminate 前先小批量观察,并提前知会应用方。

idle_session_timeout 与连接池组合时要三思。 池内的空闲连接被数据库踢掉后,池子可能感知滞后,客户端拿到「已死连接」会报错一次。如果启用了数据库侧该参数,把池侧的 server_idle_timeout 配得更短,让池子先于数据库回收连接。

idle in transaction (aborted) 说明应用吞了错误。 事务内语句报错后,PostgreSQL 会拒绝后续语句直到 ROLLBACK;应用若不回滚继续拿着连接,就进入这个状态。超时参数同样能清理它,但根因在应用的异常处理,值得给应用团队提单。

prepared transaction(两阶段提交)与预留无关但同属「悬挂」问题。 max_prepared_transactions 打开后,PREPARE TRANSACTION 之后忘记 COMMIT PREPARED 的遗留事务会长期持有锁——巡检时应顺带检查 pg_prepared_xacts。它与普通空闲事务的治理手段不同,不能靠超时自动清理。

认证失败也是连接压力。 暴力重试、配置错误的监控 agent 会以高频发起连接消耗资源。关注日志中的 password authentication failed 频次,必要时在 pg_hba.conf 层面收紧来源,并考虑 Fail2ban 类机制。

Unix socket 连接不受 tcp_keepalives_* 影响。 保活三件套只作用于 TCP 连接;本机走 socket 的监控与备份工具需要用各自的超时机制保护。

最佳实践 ​

  1. 把 max_connections 当资源预算,而不是性能旋钮。 保持 100~300 的保守区间,接入压力交给连接池(PgBouncer)。评估上调前,先用「每后端基础内存 × 预期活跃数」算一笔内存账。
  2. 始终保留预留区间。 superuser_reserved_connections 建议 5~10;PG16+ 用 reserved_connections + pg_use_reserved_connections 为监控、备份、HA 工具账号留专用配额。
  3. 实例级开启 idle_in_transaction_session_timeout,用角色级设置做差异化豁免。 这是治理空闲事务性价比最高的一招:默认 5~15 分钟,大事务账号单独放宽。
  4. PG14+ 开启 client_connection_check_interval。 让客户端异常退出后的长查询能被自动取消,成本极低、收益明确。
  5. 缩短 TCP 保活。 tcp_keepalives_idle=300 / interval=30 / count=5,把半开连接的回收时间从 2 小时级压到 8 分钟级。
  6. 为连接数建立双阈值告警并接入值班响应。 70% 预警、85% 严重;同时告警 idle in transaction 的数量与时长,而不只是总数。
  7. 巡检固定跑三条 SQL:state 分布、idle 按来源聚合、阻塞关系分析。 它们分别回答「连接健康吗」「谁在泄漏」「谁堵住了谁」,见本页语法与实战部分。
  8. terminate 操作形成标准剧本。 先画像(client_addr / application_name 聚合)→ 小批量验证 → 放量 → 每一步记录 pid 与时间,事后与应用团队复盘根因。
  9. 应用侧强制连接池与超时纪律。 池子按 default_pool_size × 组合数 < max_connections − 预留 校准;应用开启 SET LOCAL 或事务级 GUC,避免依赖会话级状态。
  10. 参数变更走 ALTER SYSTEM 并留存记录。 变更前后各导出一份相关 pg_settings 快照,变更记录进版本库;需要重启的参数用 pending_restart 跟踪,择机在维护窗口配合主备切换滚动生效。
  11. 把连接类 FATAL 日志纳入告警扫描。 too many clients already、terminating connection due to idle-in-transaction timeout、password authentication failed 的突增都各自指向不同的问题,值得分别建告警规则。

下一节

继续学习:表与索引膨胀检查

参考链接 ​