连接管理
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 的连接生命周期可以粗略分为五个阶段:
客户端 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. 终止 | 客户端退出、异常断开或被 terminate | pg_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也会推高共享内存占用。
┌──────────────── 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_connections | 100 | 早期 | 全实例最大并发连接数上限 |
superuser_reserved_connections | 3 | 早期 | 仅为超级用户(及拥有 pg_use_reserved_connections 角色的成员,见下)保留的连接名额 |
reserved_connections | 0 | PostgreSQL 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 等)。下面按运维动线把它们过一遍。
查看当前容量参数
-- 三大容量参数的当前值
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'); 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,是变更后验证「是否已等待重启」的快捷方式。
查看连接分布
-- 按数据库与状态统计当前连接
SELECT datname, state, count(*) AS cnt
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY datname, state
ORDER BY datname, cnt DESC; datname | state | cnt
----------+-----------------------+-----
app | active | 3
app | idle | 86
app | idle in transaction | 2
postgres | active | 1
(4 rows)-- 看连接的「来源画像」:谁、从哪台机器、用什么程序连的
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 统一管理:
-- 需要 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;
-- 之后择机重启实例终止连接
-- 温和:取消当前正在执行的查询(会话保留)
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 是一个资源预算参数,它同时消耗三类资源:
- 每后端的私有内存。即使每个 backend 只做最简单的事,基础内存也在 MB 量级;如果查询复杂,
work_mem的用量还会叠加。粗略估算:max_connections × (几 MB 基础内存 + 潜在 work_mem × 并发算子数)。 - 共享内存中的锁表。锁表容量为
max_locks_per_transaction × (max_connections + max_prepared_transactions + ...),max_locks_per_transaction默认 64。连接数翻倍,锁表预算也翻倍。 - 进程调度与上下文切换。几百个活跃进程同时争 CPU,内核调度开销会肉眼可见地反映在延迟抖动上。
这就是为什么生产环境的推荐路径永远是:宁可 max_connections 保守(例如 100~300),把接入压力交给连接池,而不是把它调到几千。此外,max_connections 的修改需要重启实例,配合主备切换可以实现「滚动重启」,这属于变更管理范畴。
三个预留参数的水位关系
把三个预留参数放进一张图会更直观:
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:
-- 让监控账号能使用预留连接区间
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 函数调用 | 罕见 |
disabled | track_activities 关闭时的占位 | 配置问题 |
日常巡检只需要一条「state 分布」SQL(见上文语法部分),但要看懂它,还需要配合三个时间戳列:
backend_start:连接建立时间 → 判断连接的「年龄」。xact_start:当前事务开始时间 → 长事务的判定依据(为 NULL 表示当前无事务)。state_change:状态最后一次变化的时间 →idle连接挂了多久、idle in transaction挂了多久,都用now() − state_change计算。
一套常用的「分层分析」查询:
-- 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、也可能是批处理任务卡住。
空闲事务的连锁反应:
- 阻塞 VACUUM 前进:事务的快照会钉住所有比它新的死元组,autovacuum 无法回收这些表的空间,膨胀随之而来。一个挂了一小时的空闲事务,足以让一张高频更新的表膨胀出可观的死元组。
- 持有锁不放:事务期间获取的行锁、表锁全部保持,其他会话的 DDL、更新会被阻塞,
pg_locks里出现长长的等待队列。 - 占用一个连接名额:它本身就是一条活连接,叠加连接数压力。
- 阻止 XID 回卷:长时间运行的事务还会阻止 WAL 的清理与 XID 冻结推进(严重时触发 XID 耗尽告警)。
治理空闲事务的超时参数族
PostgreSQL 为「会话/事务空闲」提供了一组防御性超时,合理配置后可以自动清理失控会话:
| 参数 | 引入版本 | 默认值 | 作用对象 | 建议起点 |
|---|---|---|---|---|
idle_in_transaction_session_timeout | 9.6 | 0(不限) | 事务处于 idle in transaction 状态的时长 | 1~15 分钟 |
idle_session_timeout | 14 | 0(不限) | 纯 idle(无事务)会话的时长 | 10~60 分钟,谨慎启用 |
client_connection_check_interval | 14 | 0(不限) | 长查询执行期间周期性检测客户端 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 配置。
# 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 = 5ALTER SYSTEM 与参数变更流程
ALTER SYSTEM 把参数写入 postgresql.auto.conf(优先级高于 postgresql.conf),是实现「可追溯参数变更」的标准方式。连接相关的变更流程建议固定为:
# 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_connections | default_pool_size × 池内 database/user 组合数 | 真实后端连接总和必须小于 max_connections 并留出预留区间 |
| 应用并发接入量 | max_client_conn | 前端连接上限,通常远大于真实后端连接数 |
idle_session_timeout | server_idle_timeout | 二选一主导,避免两边同时踢连接造成抖动 |
idle_in_transaction_session_timeout | server_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_connections | 70% | 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。以下是标准处置序列。
第一步:用预留通道连进来,确认现场。务必用超级用户或预留角色连接,普通用户此刻大概率登不进去:
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';" total | active | idle | idle_in_tx
-------+--------+------+------------
200 | 6 | 192 | 2
(1 row)第二步:给 idle 连接画像,找出泄漏来源:
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; 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 备查:
-- 先小批量试 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; 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。它们不能无脑全杀——先看它拿着什么锁、跑的是什么:
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; 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 并让应用侧重放业务。
第五步(事后):给该应用接入连接池或调整池参数,并为数据库配置兜底超时(见示例三)。
示例二:找出持有锁的空闲事务
空闲事务真正的杀伤力在于锁。当有会话报「等待锁」时,用这条查询直接定位阻塞源头:
-- 谁在等待、被谁阻塞
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)); 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 阻塞者。
示例三:超时兜底与验证
应急处理后,落一套防复发配置:
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'); name | setting
---------------------------------------+---------
client_connection_check_interval | 60000
idle_in_transaction_session_timeout | 600000
tcp_keepalives_idle | 300
(3 rows)做个小实验验证超时确实在工作:
-- 会话 A
BEGIN;
UPDATE public.orders SET note = 'hold' WHERE id = 1;
-- 然后什么都不做,等 10 分钟FATAL: terminating connection due to idle-in-transaction timeout
服务器意外地关闭了连接日志中也会留下对应记录,可纳入告警扫描:
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示例四:为不同角色设置差异化超时
超时参数既可以在实例级配置,也可以按角色覆盖。例如给报表账号更宽松的空闲事务窗口,给普通业务账号更严格的限制:
-- 实例级兜底(严格)
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 的监控与备份工具需要用各自的超时机制保护。
最佳实践
- 把
max_connections当资源预算,而不是性能旋钮。 保持 100~300 的保守区间,接入压力交给连接池(PgBouncer)。评估上调前,先用「每后端基础内存 × 预期活跃数」算一笔内存账。 - 始终保留预留区间。
superuser_reserved_connections建议 5~10;PG16+ 用reserved_connections+pg_use_reserved_connections为监控、备份、HA 工具账号留专用配额。 - 实例级开启
idle_in_transaction_session_timeout,用角色级设置做差异化豁免。 这是治理空闲事务性价比最高的一招:默认 5~15 分钟,大事务账号单独放宽。 - PG14+ 开启
client_connection_check_interval。 让客户端异常退出后的长查询能被自动取消,成本极低、收益明确。 - 缩短 TCP 保活。
tcp_keepalives_idle=300 / interval=30 / count=5,把半开连接的回收时间从 2 小时级压到 8 分钟级。 - 为连接数建立双阈值告警并接入值班响应。 70% 预警、85% 严重;同时告警
idle in transaction的数量与时长,而不只是总数。 - 巡检固定跑三条 SQL:state 分布、idle 按来源聚合、阻塞关系分析。 它们分别回答「连接健康吗」「谁在泄漏」「谁堵住了谁」,见本页语法与实战部分。
- terminate 操作形成标准剧本。 先画像(client_addr / application_name 聚合)→ 小批量验证 → 放量 → 每一步记录 pid 与时间,事后与应用团队复盘根因。
- 应用侧强制连接池与超时纪律。 池子按
default_pool_size × 组合数 < max_connections − 预留校准;应用开启SET LOCAL或事务级 GUC,避免依赖会话级状态。 - 参数变更走
ALTER SYSTEM并留存记录。 变更前后各导出一份相关pg_settings快照,变更记录进版本库;需要重启的参数用pending_restart跟踪,择机在维护窗口配合主备切换滚动生效。 - 把连接类 FATAL 日志纳入告警扫描。
too many clients already、terminating connection due to idle-in-transaction timeout、password authentication failed的突增都各自指向不同的问题,值得分别建告警规则。
下一节
继续学习:表与索引膨胀检查
参考链接
- PostgreSQL 官方文档:Connections and Authentication
- PostgreSQL 官方文档:pg_stat_activity 视图
- PostgreSQL 官方文档:Client Connection Check Interval 等 PG14 新特性(Release Notes)
- PostgreSQL 官方文档:Statement Behavior(超时参数说明)
- PostgreSQL 官方文档:ALTER SYSTEM
- PostgreSQL 官方文档:服务器管理函数(pg_terminate_backend 等)
- PostgreSQL 官方文档:Database Roles(预置角色 pg_use_reserved_connections)
- PostgreSQL 官方文档:Connection Pooling 相关 FAQ
- PgBouncer 官方文档:Configuration
- PostgreSQL Wiki:Performance Optimization 与连接