Skip to content

pg_stat_activity 实时会话 ​

pg_stat_activity 是 PostgreSQL 所有监控手段里最常用、性价比最高的一个视图。它不需要安装任何扩展,也不需要修改任何配置,只要能连上数据库,就能看到「此刻服务器上每一个后端进程正在做什么」:是谁、从哪台机器连进来的、正在执行哪条 SQL、处于什么状态、在等什么资源、事务从什么时候开始。几乎所有线上故障的第一现场排查——CPU 打满、连接暴涨、SQL 卡住、事务长期不提交——都从这里开始。

它是一个集簇级(cluster-wide)视图:一行对应一个 server 进程,包含本集簇内所有数据库的连接,而不仅仅是你当前连接的数据库。这一点经常被误解。它同时也是实时视图:每次查询都会从共享内存重新读取最新状态,不存在「统计延迟」或「需要刷新」的问题,看到的永远是此刻。

本页是这个监控章节的起点。先把字段、状态、等待事件这三块地基打牢,后面的锁监控(pg_locks)、SQL 统计(pg_stat_statements)都会反复引用这里的概念。

前置知识

建议先阅读:数据脱敏方案

基础概念 ​

PostgreSQL 采用「每连接一个进程」的模型:客户端每建立一个连接,服务端就 fork 一个 backend 进程为其服务,此外还有 checkpointer、background writer、WAL writer、autovacuum launcher、autovacuum worker、walsender 等一批辅助进程。pg_stat_activity 的设计就是「一行 = 一个进程」,把这些进程的实时状态全部暴露出来。

概念说明
backend为一个客户端连接服务的后端进程,是视图的主要观察对象
辅助进程checkpointer、background writer、WAL writer 等常驻进程,也各占一行
walsender流复制/逻辑复制场景下的发送进程,同样出现在视图里
state会话当前的宏观状态:执行中、空闲、空闲事务等
wait_event_type / wait_event进程此刻在等什么:一对「等待事件类型 + 等待事件」标签
backend_xid / backend_xmin进程持有的事务 ID 与最小事务快照(决定能否 VACUUM 的关键)
pg_read_all_stats预定义角色,非超级用户想看全量字段需要授予它

理解这个视图的三个前提:

  1. 它是实时快照,不是历史记录。 视图没有时间维度,查两次结果不同是正常的。想留历史就必须自己周期性采样(脚本、pg_stat_statements 或外部监控系统)。
  2. 它是集簇级的。 无论连到哪个数据库执行查询,都能看到整个集簇全部数据库的会话。要只看某个库,需要自己加 WHERE datname = ... 过滤。
  3. 字段可见性受权限控制。 普通用户能看到所有行,但 query、query_id、backend_xid 等属于「他人」会话的字段会被置空;授予预定义角色 pg_read_all_stats(或超级用户)才能看全。

视图在监控体系中的位置 ​

PostgreSQL 的统计视图可以分为三层,pg_stat_activity 属于最上层、最即时的那一层:

层次代表视图回答的问题数据性质
实时会话pg_stat_activity现在谁在连、在干什么、在等什么实时快照
累计统计pg_stat_database、pg_stat_user_tables、pg_stat_user_indexes从上次重置到现在累计发生了多少累计计数
SQL 级统计pg_stat_statements哪些 SQL 最耗时、最耗资源(扩展)聚合归一化

排查「现在出了什么问题」用第一层;回答「平时哪里最重」用后两层。本页聚焦第一层,后两层分别在本章后续页面展开。

语法与用法 ​

pg_stat_activity 是普通视图,直接 SELECT 即可。最常用的查询形态如下:

sql
-- 1. 看全貌:每个会话的基本信息(常用列)
SELECT pid,
       datname,
       usename,
       application_name,
       client_addr,
       state,
       wait_event_type,
       wait_event,
       query_start,
       query
FROM pg_stat_activity
ORDER BY pid;
text
  pid  | datname  | usename | application_name | client_addr  | state | wait_event_type | wait_event |         query_start         |            query
-------+----------+---------+------------------+--------------+-------+-----------------+------------+-----------------------------+-------------------------------
  3111 | appdb    | app     | myapp-pool-1     | 10.0.0.11    | idle  |                 |            | 2026-09-30 09:12:04.521+08  | SELECT * FROM orders WHERE ...
  3128 | appdb    | app     | myapp-pool-2     | 10.0.0.11    | active|                 |            | 2026-09-30 09:14:31.002+08  | UPDATE accounts SET balance ...
  3140 | postgres | postgres| psql             |              | active|                 |            | 2026-09-30 09:14:30.988+08  | SELECT pid, ... FROM pg_stat_activity
  3155 | appdb    | app     | autovacuum worker|              | active|                 |            | 2026-09-30 09:10:00.113+08  | autovacuum: VACUUM public.logs
(4 rows)
sql
-- 2. 只看当前数据库的业务会话(排除自己和后台进程)
SELECT pid, usename, state, wait_event_type, wait_event, query
FROM pg_stat_activity
WHERE datname = current_database()
  AND pid <> pg_backend_pid()
  AND backend_type = 'client backend';

-- 3. 正在执行的 SQL,按执行时长倒序
SELECT pid,
       now() - query_start AS query_duration,
       state,
       left(query, 80) AS query_head
FROM pg_stat_activity
WHERE state = 'active'
  AND backend_type = 'client backend'
ORDER BY query_duration DESC;

-- 4. 看视图的完整定义(确认本版本有哪些字段)
SELECT pg_get_viewdef('pg_stat_activity'::regclass, true);

在 psql 里也可以用元命令直接查看列结构:

text
\d pg_stat_activity

后面所有章节的示例都建立在这些基本查询形态之上。

详细说明 ​

字段全览 ​

pg_stat_activity 的字段可以分成五组来记忆。下表以 PG14 及以后版本为准,各版本差异在「版本演进」一节说明。

标识组——这条连接是谁:

字段类型说明
pidinteger该后端进程的 OS 进程号,是所有后续操作(kill、关联 pg_locks)的主键
datid / datnameoid / name该会话连接的数据库 OID 与名称
usesysid / usenameoid / name登录角色的 OID 与名称
application_nametext客户端自报的应用名(连接串里可随意设置,需结合连接来源核实)
client_addrinet客户端 IP;走 Unix 域套接字时为 NULL
client_hostnametext客户端主机名,仅当开启反向 DNS 解析(log_hostname)才有值,一般不用
client_portinteger客户端 TCP 端口;本地套接字连接为 -1

时间组——这条连接的生命线:

字段类型说明
backend_starttimestamptz进程启动时间(即连接建立时间)
xact_starttimestamptz当前事务开始时间;不在事务中为 NULL
query_starttimestamptz当前(或最近一条)SQL 开始时间
state_changetimestamptzstate 最近一次变化的时间(如从 active 变为 idle 的时刻)

等待组——它在等什么:

字段类型说明
wait_event_typetext等待事件类型,共 9 类,见下一小节
wait_eventtext具体等待事件名;没在等待时两者皆为 NULL

状态与事务组:

字段类型说明
statetext会话宏观状态,六种取值,见下下小节
backend_xidxid该后端当前持有的顶层事务 ID;没有则为 NULL
backend_xminxid该后端的 xmin 水位,长期不推进会阻碍 VACUUM 清理死元组

查询组:

字段类型说明
querytext当前正在执行的 SQL;idle 会话显示最近一条;文本已截断处理由内部完成
query_idbigint内核计算的查询标识(PG14+),compute_query_id 启用时才有值
backend_typetext进程类型,如 client backend、autovacuum worker、walsender 等

另有 leader_pid(PG13+):若该进程是并行查询的 worker,此列是并行组 leader 的 pid;leader 自身此列为 NULL。它用于把并行 worker 与发起查询的会话关联起来。

state 的六种取值 ​

state 只对 client backend(普通客户端会话)有意义,取值有六种:

取值含义运维视角
active正在执行一条 SQL数量长期偏高说明 CPU/IO 压力大,重点看 wait_event_type
idle空闲,等待客户端发下一条命令正常状态;数量过大考虑连接池
idle in transaction打开了事务但当前没有执行中的 SQL高危:会长期持有快照与锁,阻塞 VACUUM,重点治理对象
idle in transaction (aborted)事务内某条语句出错且未回滚,连接仍挂着应用吞了异常没 rollback,处理方式同上
fastpath function call正在执行 fast-path 函数调用少见,基本不用关心
disabled被禁用了状态跟踪(track_activities = off)检查是谁关了统计开关

idle in transaction 是生产环境最常见的「隐形杀手」

一个挂了 20 分钟的 idle in transaction 会话会一直持有事务快照:它的 backend_xmin 之前的所有死元组都无法被 VACUUM 回收(表膨胀),它持有的行锁会让其他会话排队,它的连接白白占用 max_connections 配额。发现它,先看 xact_start 判断挂了多久,再用 pg_terminate_backend() 处理,并通知应用侧修复「开了事务不提交/不回滚」的代码路径。

后台辅助进程(checkpointer、WAL writer 等)不属于客户端会话,state 一般显示为空,不在上述六种取值之列。

等待事件:wait_event_type 的九类 ​

当一个进程「没有在跑代码,而是在等什么东西」时,内核会给它打上一对标签:wait_event_type(等待什么类别)+ wait_event(具体等哪个)。这是排障时信息密度最高的两个字段。

wait_event_type 共有九类:

类型在等什么典型场景
LWLock轻量级锁(内部数据结构的短期互斥)多并发下的共享内存结构竞争
Lock重型锁(表锁、行锁、事务锁、顾问锁)锁等待、行锁排队
BufferPin缓冲区被钉住(其他会话长期持有所需缓冲的 pin)长事务阻碍 VACUUM 移动元组
Activity后台进程的空转循环等待checkpointer、walwriter 等正常休眠
Client与客户端交互(读请求/写响应)应用不取结果、网络慢、连接假死
Extension扩展模块自定义的等待点取决于具体扩展
IPC进程间通信(并行 worker、消息队列等)并行查询同步
Timeout定时器到点(sleep、延迟)pg_sleep()、vacuum 延迟
IO文件读写全表扫描、WAL 落盘、排序落盘

几点解读经验:

  • Activity 类多见于辅助进程的空转等待,一般是正常现象;业务 backend 出现 Client 等待要看是不是应用侧处理太慢。
  • Lock + wait_event = transactionid 是「会话 A 在等 B 的行锁提交/回滚」的标志,直接跳到锁监控页看阻塞链。
  • IO 类集中在 DataFileRead、DataFileWrite、WALWrite、WALSync 等文件操作上,长期大量出现要怀疑磁盘瓶颈或 shared_buffers 太小。

常见 wait_event 速查 ​

下列事件名均出自官方文档,属于各类型中出镜率最高的一批。注意不同大版本对部分事件命名做过调整(尤其较新版本把部分驼峰命名改成了连字符风格),以你所用版本的官方 Wait Event 附录为准。

wait_event_typewait_event含义
Locktransactionid等待某个事务提交或回滚(典型:等行锁持有者)
Locktuple等待某个元组上的锁(并发更新同一行)
Lockrelation等待表级锁
Lockextend等待扩展关系文件的锁
Lockvirtualxid等待虚拟事务 ID 锁
Lockadvisory等待顾问锁
LWLockXidGenLock等待事务 ID 分配器
LWLockProcArrayLock等待进程数组(快照获取、事务结束时排队)
LWLockbuffer_content等待缓冲页内行数据的轻量锁
LWLockbuffer_mapping等待缓冲区映射表
ClientClientRead等待客户端发来数据
ClientClientWrite等待能把结果写给客户端
IODataFileRead读数据文件
IODataFileWrite写数据文件
IOWALWrite写 WAL
IOWALSyncWAL 落盘同步(fsync)
IPCParallelFinish等待并行 worker 全部结束
IPCMessageQueueInternal等待并行消息队列
TimeoutPgSleeppg_sleep() 休眠
TimeoutVacuumDelayautovacuum 按成本延迟休眠
BufferPinBufferPin等待缓冲区 pin 释放

时间戳字段怎么用 ​

四个时间戳刻画了连接生命周期的不同节点,用 now() 与它们做差是最常用的量化手段:

sql
SELECT pid,
       now() - backend_start           AS connection_age,   -- 连接建立了多久
       now() - xact_start              AS transaction_age,  -- 当前事务开了多久
       now() - query_start             AS query_age,        -- 当前(或最近一条)SQL 跑了多久
       now() - state_change            AS idle_age,         -- 空闲了多久
       now() - state_change            AS since_state_change
FROM pg_stat_activity
WHERE backend_type = 'client backend';
  • 排查慢 SQL:看 active 会话的 now() - query_start。
  • 排查长事务:看所有会话的 now() - xact_start,注意 idle in transaction 也要算。
  • 排查连接泄漏:idle 会话的 now() - state_change 巨大且数量持续增长,多半是应用没还连接。
  • state_change 与 query_start 的区别容易混淆:query_start 记录的是「最近一条 SQL 的开始时刻」,state_change 记录的是「状态切换时刻」。对 idle 会话,query_start 是它最后执行的那条 SQL 的开始时间,state_change 是它执行完进入 idle 的时间,两者相减约等于最后那条 SQL 的耗时。

backend_type:视图里不只有业务连接 ​

backend_type 区分了进程角色,常见的取值包括:

取值说明
client backend普通客户端会话(业务连接)
autovacuum launcher / autovacuum worker自动清理的调度进程与工作进程
walsender流复制/逻辑复制的发送进程
background writer / checkpointer / walwriter常驻辅助进程
parallel worker并行查询的工作进程(配合 leader_pid 定位所属会话)
logical replication launcher / logical replication worker逻辑复制相关进程

做连接数巡检时,务必用 backend_type = 'client backend' 过滤出真正的业务连接,否则统计会被辅助进程污染。反过来,排查 autovacuum 行为时,就专门看 autovacuum worker 那几行的 query 列(内容形如 autovacuum: VACUUM public.xxx)。

权限与可见性 ​

  • 超级用户与被授予 pg_read_all_stats 角色的用户可以看到所有行的全部字段。
  • 普通用户虽然能看到所有行,但他人会话的 query、query_id、backend_xid、backend_xmin、client_addr 等敏感字段会被置空,只能看到自己会话的完整信息。
  • application_name 是客户端自报的字符串,可以伪造,安全审计时不要把它当作可信依据;可信的连接来源是 client_addr(结合认证日志核对)。
sql
-- 授予监控账号查看全量统计的权限
GRANT pg_read_all_stats TO monitor_user;

终止会话:pg_cancel_backend 与 pg_terminate_backend ​

看清楚了要动手,用到两个内置函数:

函数作用相当于影响范围
pg_cancel_backend(pid)取消该会话当前正在执行的一条 SQL客户端收到 SIGINT仅当前语句回滚,连接保留,事务未结束
pg_terminate_backend(pid)终止整个会话客户端收到 SIGTERM连接断开,未提交事务回滚
sql
-- 温和手段:先取消正在跑的语句
SELECT pg_cancel_backend(3128);

-- 强制手段:直接断开该会话
SELECT pg_terminate_backend(3128);

-- PG14+ 支持带超时的终止:最多等 5000ms 让会话自行退出
SELECT pg_terminate_backend(3128, 5000);

使用顺序建议:先 cancel、后 terminate。cancel 只影响当前语句,对业务冲击小;但它对 idle in transaction 会话无效(没有正在执行的语句可取消),这种会话只能 terminate。kill 之后客户端会收到连接错误,应用需要能重连——这也是为什么强调应用侧要有健壮的连接池。

相关超时参数:让系统自动兜底 ​

pg_stat_activity 里的问题状态,多数可以靠超时参数提前拦截,避免人工盯梢:

参数拦截什么作用范围
statement_timeout单条 SQL 执行超时会话/角色/事务级设置
lock_timeout获取锁的等待超时会话/角色/事务级设置
idle_in_transaction_session_timeout事务打开后空闲超过多久会话/角色/实例级设置
idle_session_timeout整个会话空闲超过多久(PG14+)会话/角色级设置
sql
-- 例:给报表角色设置 10 分钟语句超时与 5 分钟空闲事务超时
ALTER ROLE report_user SET statement_timeout = '10min';
ALTER ROLE report_user SET idle_in_transaction_session_timeout = '5min';

与这些参数相关的锁等待语义,见本篇稍后的锁监控章节以及《锁等待与超时》一页。

版本演进速览 ​

pg_stat_activity 的字段比较稳定,但有几个版本节点值得知道:

  • PG13:新增 leader_pid,并行查询的 worker 可以关联回 leader 会话。
  • PG14:新增 query_id(依赖内核参数 compute_query_id);新增 idle_session_timeout 参数。
  • PG16 及以后:等待事件清单持续扩充(如逻辑复制 apply 相关的 applytransaction 等锁事件),backend_type 取值也随新后台进程增加。
  • 更早版本(PG10 以前)的 wait_event 机制尚不存在,老库排障只能靠 state + pg_locks 猜。

快照语义:统计视图的事务级缓存 ​

pg_stat_activity 虽然号称「实时」,但它的读取有一个容易忽略的细节:统计视图的数据在事务内会被缓存成快照。同一个事务里第一次访问任何统计视图时,服务器会取一份完整的统计快照并缓存;事务结束前,后续对统计视图的读取都返回同一份缓存——即使这期间其他会话的连接与状态已经发生了变化。

sql
BEGIN;

-- 第一次访问统计视图,此刻建立快照
SELECT count(*) FROM pg_stat_activity WHERE state = 'active';

-- ……期间其他会话有新连接、有断开、有新查询……

-- 第二次读取:返回的是同一份快照,数字不变
SELECT count(*) FROM pg_stat_activity WHERE state = 'active';

-- 主动丢弃快照后再读,才能看到新状态
SELECT pg_stat_clear_snapshot();
SELECT count(*) FROM pg_stat_activity WHERE state = 'active';

COMMIT;

这对写监控脚本的人有三个直接影响:

  1. 巡检脚本用自动提交模式。 psql 默认每条语句一个事务,每条查询都能拿到新快照;把多条统计查询包进同一个 BEGIN ... COMMIT 反而会让后面的查询读到旧数据。
  2. 长事务里的统计查询会「失明」。 一个开了几个小时的事务若中途访问过统计视图,之后读到的都是几小时前的快照;在长事务内做监控判断尤其危险。
  3. 需要事务内多次读取时,调用 pg_stat_clear_snapshot()。 开销只是重新取一份快照,在监控循环里按需调用即可。

采样落库:为实时视图补上时间维度 ​

实时快照回答「现在」,回答不了「过去 30 分钟连接数怎么变化的」。标准做法是周期性把聚合结果写入自建历史表:

sql
-- 历史表(一次性创建)
CREATE TABLE pg_activity_history (
    captured_at      timestamptz NOT NULL DEFAULT now(),
    datname          text,
    usename          text,
    application_name text,
    state            text,
    wait_event_type  text,
    sessions         integer NOT NULL
);

-- 每分钟执行一次的采样语句
INSERT INTO pg_activity_history
    (datname, usename, application_name, state, wait_event_type, sessions)
SELECT datname, usename, application_name, state, wait_event_type, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY datname, usename, application_name, state, wait_event_type;

-- 历史表只保留 14 天
DELETE FROM pg_activity_history
WHERE captured_at < now() - interval '14 days';

有了历史表,「昨晚 23 点连接为什么暴涨」就变成一条普通的聚合查询。更完整的历史分析(SQL 级)由 pg_stat_statements 承担;会话级的过程证据还可以配合本章日志部分的时间线互相印证。

排障 SOP:从现象到字段 ​

把视图字段与常见故障现象对应起来,是本页最重要的「速查资产」:

现象第一眼看什么判断依据典型动作
CPU 打满state = 'active' 且等待事件为空的会话等待事件为空 = 纯 CPU 运算找出最长 query,EXPLAIN 优化
连接数暴涨按 application_name / client_addr 分组计数某来源异常集中定位应用,必要时 terminate 空闲连接
SQL 卡住wait_event_type = 'Lock'在等锁而非在算pg_blocking_pids() 追阻塞链
事务长期不提交idle in transaction + xact_start空闲事务挂起通知应用修复;超时 terminate
复制发送异常backend_type = 'walsender' 的等待事件WALWrite/WALSync 堆积或无 walsender 行查网络与磁盘(配合复制监控页)
磁盘 IO 饱和wait_event_type = 'IO' 的分布DataFileRead 集中查命中率与缺失索引

值班时按这张表「对号入座」,大多数场景两三步内就能把问题分流到具体专题页。

后台进程行同样值得看 ​

backend_type 的过滤别只用来「排除」后台进程,它们本身就是观测点:

  • walsender 行:流复制的发送进程。它的 state 长期 active、等待事件集中在 WALWrite / WALSync 时,说明发送侧有压力;一行都没有则说明复制已断开(详见《pg_stat_replication 监控》)。
  • autovacuum worker 行:query 列显示它正在清理哪张表(形如 autovacuum: VACUUM public.orders)。巡检时确认「热点表是否有 worker 在光顾」,与 pg_stat_user_tables.last_autovacuum 互相印证。
  • parallel worker 行:配合 leader_pid 可以统计每个并行查询占用了多少额外进程,评估 max_parallel_workers 是否吃紧。
  • checkpointer / background writer 行:它们几乎总在 Activity 类等待中休眠;出现在 IO 等待且持续很久时,值得结合检查点统计排查(见《pg_stat_database 与 pg_stat_user_tables》页)。

实战示例 ​

示例一:日常连接巡检 ​

生产环境最常用的「一眼看全局」查询,按状态分组汇总业务连接:

sql
SELECT datname,
       usename,
       application_name,
       state,
       count(*) AS connections
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY datname, usename, application_name, state
ORDER BY connections DESC;
text
 datname | usename | application_name |        state        | connections
---------+---------+------------------+---------------------+-------------
 appdb   | app     | myapp-pool-1     | idle                |          42
 appdb   | app     | myapp-pool-2     | idle                |          38
 appdb   | app     | myapp-pool-1     | active              |           9
 appdb   | app     | report-tool      | idle in transaction |           2
 postgres| postgres| psql             | active              |           1
(5 rows)

idle 数量远大于 active 说明连接池配大了;出现 idle in transaction 则要立刻定位那条会话。

示例二:长事务与空闲事务排查 ​

sql
SELECT pid,
       datname,
       usename,
       state,
       now() - xact_start  AS xact_age,
       now() - state_change AS idle_age,
       backend_xid,
       backend_xmin,
       left(query, 60) AS last_query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
  AND now() - xact_start > interval '5 minutes'
ORDER BY xact_start;

对超过阈值的 idle in transaction 会话,处理动作:

sql
-- 先看清楚是什么(application_name、client_addr、query 都确认一遍)
SELECT pid, usename, application_name, client_addr, xact_start, query
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)');

-- 确认无业务风险后终止
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND now() - state_change > interval '15 minutes';

示例三:等待事件分布——判断瓶颈在 CPU、锁还是 IO ​

sql
SELECT wait_event_type,
       wait_event,
       count(*) AS sessions
FROM pg_stat_activity
WHERE state = 'active'
  AND backend_type = 'client backend'
GROUP BY wait_event_type, wait_event
ORDER BY sessions DESC;
text
 wait_event_type | wait_event     | sessions
-----------------+----------------+----------
                 |                |        6      <- 纯 CPU 运算,无等待
 Lock            | transactionid  |        4      <- 行锁等待,去看 pg_locks
 IO              | DataFileRead   |        3      <- 磁盘读,检查缓存/索引
 Client          | ClientRead     |        2      <- 客户端收数据慢
(4 rows)

读法:等待事件为空且 active 多,瓶颈在 CPU;Lock 多,去查阻塞链;IO 多,看命中率与磁盘;Client 多,问题可能在应用侧。

示例四:定位并行查询与它的 worker ​

sql
-- leader 与其 worker 的对应关系
SELECT pid,
       leader_pid,
       backend_type,
       state,
       left(query, 50) AS query_head
FROM pg_stat_activity
WHERE query_id IS NOT NULL
  AND backend_type IN ('client backend', 'parallel worker');

worker 行的 leader_pid 即发起查询的会话;要终止整个并行查询,kill leader 即可,worker 会随之退出。

示例五:连接数逼近上限的应急处理 ​

sql
-- 当前连接数与上限
SELECT count(*) AS current_connections,
       current_setting('max_connections') AS max_connections
FROM pg_stat_activity;

-- 找出连接数最多的客户端来源
SELECT client_addr, application_name, usename, count(*) AS n
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY client_addr, application_name, usename
ORDER BY n DESC;

-- 应急:批量断开某台失控机器的空闲连接(保留正在执行的)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE client_addr = '10.0.0.11'
  AND state = 'idle'
  AND application_name = 'myapp-pool-1';

长期解法是接入连接池,见《连接池配置(PgBouncer)》一页。

示例六:连接泄漏的追踪闭环 ​

现象:连接使用率每周稳步上涨,重启应用后回落——典型的连接泄漏。追踪流程三步:

sql
-- 1. 确认泄漏画像:按来源分组看空闲连接的「年龄」
SELECT client_addr,
       application_name,
       usename,
       count(*) AS idle_conns,
       max(now() - state_change) AS oldest_idle
FROM pg_stat_activity
WHERE state = 'idle'
  AND backend_type = 'client backend'
GROUP BY 1, 2, 3
HAVING count(*) > 10
ORDER BY oldest_idle DESC;
text
 client_addr | application_name | usename | idle_conns | oldest_idle
-------------+------------------+---------+------------+--------------
 10.0.0.11   | myapp-pool-1     | app     |         46 | 3 days 04:12
(1 row)
sql
-- 2. 保留现场证据(写入采样历史表,便于与发布时间对照)
INSERT INTO pg_activity_history
    (datname, usename, application_name, state, wait_event_type, sessions)
SELECT datname, usename, application_name, state, wait_event_type, 1
FROM pg_stat_activity
WHERE client_addr = '10.0.0.11' AND state = 'idle';

-- 3. 应急释放:只断开长时间空闲的连接(active 连接不受影响)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE client_addr = '10.0.0.11'
  AND state = 'idle'
  AND now() - state_change > interval '1 hour';

闭环动作:把第 1 步的查询接入每日巡检,oldest_idle 超过一天即告警;反馈应用团队检查连接归还逻辑。连接数的长期治理方案见《连接池配置(PgBouncer)》。

注意事项 ​

不要拿 application_name 做安全判断。 它是客户端连接串里的一个任意字符串,伪造零成本。审计归审计(client_addr + 认证日志),业务归业务(用 application_name 分组统计)。

pg_stat_activity 是集簇级视图。 连在哪个库上查询都能看到全部会话;反过来,想让监控只针对某个库,必须显式 WHERE datname = ...。忘记过滤是「监控数字比预期大一圈」的常见原因。

统计开关被关闭时视图会失真。 track_activities = off 时,query、等待事件等字段不再更新,所有会话 state 显示 disabled。排障前先确认这个开关没有被误关。

pg_cancel_backend 对空闲事务无效。 没有「当前语句」可取消。要处理 idle in transaction 只能 pg_terminate_backend。另外 cancel 只是请求,极端情况下(比如被 C 语言层面的无限循环卡住)可能不生效,届时仍需 terminate。

kill autovacuum worker 要三思。 用 pg_terminate_backend 杀掉 autovacuum worker 会中断该表的清理进度(已做的工作不白费,但重做有成本)。反复手工杀 autovacuum 会让表膨胀越积越深,正确做法是调整 autovacuum 参数或优化大事务。

视图查询本身也会出现在视图里。 你自己执行的那条查询 SELECT ... FROM pg_stat_activity 也会有一行,pid 等于 pg_backend_pid()。写巡检脚本时记得排除自己,避免统计口径被污染。

时间差计算注意时区与时钟。 now() 返回的是事务开始时间而非当前时刻——长事务里反复调用 now() 得到的是同一个值。在会话内做时间差计算,请用 clock_timestamp()。

query 文本可能被截断与脱敏。 视图里的 query 有长度上限(超长语句会截断),且对无权限用户直接置空。需要完整 SQL 时应结合日志系统(本章节日志部分)而不是单纯依赖该视图。

最佳实践 ​

  1. 把「连接巡检 SQL」固化成脚本或监控项。 按状态分组的连接数、最老事务的 xact_age、idle in transaction 数量,这三个指标每天都要有人(或系统)看一眼,任何异常增长都值得追查。
  2. 用 backend_type = 'client backend' 圈定业务连接。 所有连接数指标、连接池容量规划都应基于业务连接口径,而不是视图总行数。
  3. 为每个应用约定独立的 application_name。 排障时能一眼分辨连接来自哪个服务、哪个连接池;配合连接池时尤其重要(否则池里连接张冠李戴)。
  4. 给关键角色预置超时参数兜底。 报表、ETL 等低信任角色设置 statement_timeout 与 idle_in_transaction_session_timeout,让系统自动回收失控会话,而不是等人工发现。
  5. 把 idle in transaction 当作告警级事件。 任何超过数分钟的空闲事务都应该触发告警:它同时引发锁等待、表膨胀、复制延迟三重问题。
  6. 排障顺序:state → wait_event → 时间差 → query。 先看状态分组找异常群体,再用等待事件判断瓶颈类型,再用时间戳量化严重程度,最后才逐条看 SQL。不要一上来就逐行读 query 文本。
  7. 先 cancel 后 terminate,并留操作记录。 终止会话是侵入性操作,记录 pid、SQL、时间与批准人;对 autovacuum、流复制等系统进程更是如此。
  8. 授予监控账号 pg_read_all_stats 而不是超级用户。 监控只需要读统计的权限;把超级用户交给监控脚本是常见的安全坏习惯。
  9. 升级前核对版本差异。 query_id(PG14+)、leader_pid(PG13+)等字段并非所有版本都有;写跨版本通用的巡检 SQL 时用 pg_get_viewdef 或查询 information_schema.columns 做能力探测。
  10. 实时快照留不住,历史数据要自己采。 对 pg_stat_activity 做周期采样(比如每 10 秒落一张表),才能回答「昨晚 11 点连接为什么暴涨」这类事后问题;更长期的 SQL 级统计交给下一章的 pg_stat_statements。
  11. 把等待事件纳入长期监控维度。 wait_event_type 分布是免费的「瓶颈画像」,按小时聚合后能直观看到系统从 IO 型到锁型瓶颈的迁移趋势。

下一节

继续学习:pg_locks 锁监控

参考链接 ​