Skip to content

进程模型(Postmaster / Backend) ​

PostgreSQL 采用「多进程(Multi-Process)」模型:一个主进程负责管理,每条客户端连接对应一个独立的后端进程。本节讲清它为什么不用多线程、主进程与各类辅助进程的分工、进程间通信的三种手段,以及如何用 pg_stat_activity 观察当前实例的进程全景。

前置知识

阅读本节前,建议先了解:PostgreSQL 架构概览

基础概念 ​

PostgreSQL 的进程分为三类:

类别英文名诞生方式数量生命周期
主进程Postmaster服务启动1随实例
辅助进程Auxiliary / Background Process主进程 fork固定若干随实例
后端进程Backend Process主进程 fork(每个连接一个)动态随连接
工作进程Worker Process主进程 fork 或按需派生动态随任务
术语英文名说明
多进程模型Multi-Process Model每个连接一个操作系统进程
多线程模型Multi-Threaded Model每连接一个线程,共享同一地址空间
派生(分叉)fork父进程复制自身创建子进程的系统调用
信号Signal进程间异步通知机制(SIGHUP、SIGTERM 等)
闩锁LatchPostgreSQL 内部的轻量同步原语,类似自旋锁
轻量锁LWLock, Lightweight Lock保护共享内存数据结构的锁
进程数组ProcArray共享内存中记录所有活跃进程的快照信息
后端类型backend_typepg_stat_activity 中标识进程角色的列

多进程 vs 多线程 ​

这是理解 PostgreSQL 架构的第一个关键决策点:

维度多进程(PostgreSQL)多线程(MySQL / Oracle)
地址空间各进程独立,隔离性好共享,一处越界影响全局
崩溃影响单个后端崩溃通常不影响其他连接单线程崩溃可能拖垮整个实例
创建开销较高(fork + COW 页表复制)较低
数据共享需借助共享内存,数据须可序列化直接读写同一变量
调试难度便于 gdb attach 单个进程线程级调试更复杂
并发上限进程数受系统限制(数千)线程更易上到数万
保护机制由 OS 内存保护强制隔离依赖程序员自觉

PostgreSQL 诞生于 1986 年,当时 Unix 线程实现尚不成熟且不可移植,因此选择多进程。这个决策带来两个直接后果:

  1. 连接池是刚需:fork 一个进程的成本远高于创建一个线程,高并发短连接场景必须用 PgBouncer 之类的连接池。
  2. 共享内存是核心:所有跨进程的数据(buffer、锁、事务状态)都必须放在共享内存里,且结构必须固定布局——这也是 PostgreSQL 大量使用固定大小数组和 OID 的原因。

为什么单个后端崩溃不会拖垮实例

fork 出的每个进程拥有独立地址空间,某个后端因 bug 段错误(Segmentation Fault)时,操作系统只会终止该进程。Postmaster 通过 waitpid() 感知到子进程非正常退出,会先终止所有其他后端(因为共享内存状态可能已被写坏),然后重建共享内存并进入恢复流程。所以「后端崩溃」的后果是全体重启 + 崩溃恢复,而不是数据静默损坏。

核心机制 ​

主进程的职责 ​

Postmaster 是唯一以 postgres 可执行文件形式常驻的进程(其他进程都是它的 fork 结果)。它的职责有四项:

职责实现方式
监听连接select() / poll() 监听 TCP 端口与 Unix 套接字,同时监听自管道(self-pipe)
派生后端接受连接后 fork() 一个后端进程,把套接字文件描述符传给它
监控子进程通过 waitpid() 收集子进程退出状态,异常退出则触发重启
崩溃恢复重启时重放 WAL 到最近的检查点之后,恢复一致性

Postmaster 本身不处理 SQL。它只在启动阶段读取配置、初始化共享内存,之后进入「监听循环」——这是它保持极简与稳定的关键。

bash
# 查看 Postmaster 的启动命令(关键是 -D 指定的数据目录)
ps -p $(head -1 /var/lib/postgresql/18/main/postmaster.pid) -o pid,ppid,user,args
text
  PID  PPID USER     COMMAND
 1147     1 postgres /usr/lib/postgresql/18/bin/postgres -D /var/lib/postgresql/18/main -c config_file=/etc/postgresql/18/main/postgresql.conf
bash
# postmaster.pid 记录了实例身份信息(用于防止双启动)
head -5 /var/lib/postgresql/18/main/postmaster.pid
text
1147                                  # 主进程 PID
/var/lib/postgresql/18/main           # 数据目录
1774570332                            # 启动时间戳
5432                                  # 端口
/var/run/postgresql                   # Unix socket 目录

postmaster.pid 的危险操作

绝不要手工删除 postmaster.pid。如果实例实际在运行而 pid 文件被删除,再启动一个新实例会导致两个 Postmaster 同时写同一份数据文件 → 数据损坏。若确实存在残留(如断电后文件系统未同步),先确认没有 postgres 进程在运行,再删除。

辅助进程清单 ​

PostgreSQL 18 的辅助进程(可用 pg_stat_activity.backend_type 观察到):

backend_type是否常驻职责后续章节
checkpointer是执行检查点,刷新所有脏页,缩短恢复时间Checkpointer
background writer是在后台渐进刷脏页,平滑 I/OBackground Writer
walwriter是把 WAL 缓冲刷入磁盘WAL Writer
autovacuum launcher是调度 autovacuum workerAutoVacuum Worker
autovacuum worker按需对一个表执行 VACUUM / ANALYZEAutoVacuum Worker
archiver是(启用归档时)把完成的 WAL 段拷贝到归档目录Archiver / Stats Collector
walreceiver从库上接收主库的 WAL 流—(复制章节)
walsender主库上按需向从库发送 WAL 流—(复制章节)
logical replication launcher是启动逻辑复制 apply worker—(复制章节)
parallel worker按需并行查询的工作进程—(查询优化章节)
client backend按连接处理客户端 SQL 请求Backend 后端进程
sql
-- 查看当前实例所有进程角色及其状态
SELECT backend_type,
       count(*)                              AS processes,
       string_agg(DISTINCT state, ', ')      AS states
FROM pg_stat_activity
GROUP BY backend_type
ORDER BY processes DESC, backend_type;
text
          backend_type          | processes |       states
--------------------------------+-----------+---------------------
 client backend                 |         5 | active, idle
 checkpointer                   |         1 |
 walwriter                      |         1 |
 background writer              |         1 |
 autovacuum launcher            |         1 |
 logical replication launcher   |         1 |
(6 rows)

每连接一进程 ​

从客户端发起连接到拥有一个专属进程,完整流程如下:

text
客户端                     Postmaster                     新 Backend
  │                           │                              │
  │── TCP SYN / connect ─────►│                              │
  │                           │ ① accept() 得到新 socket      │
  │                           │ ② fork() ───────────────────►│
  │                           │     (继承监听 socket,       │
  │                           │      但立即关闭它)           │
  │                           │ ③ 父进程 close(new socket)    │
  │◄─── Authentication ───────┼──────────────────────────────│
  │─── StartupMessage ────────┼─────────────────────────────►│
  │                           │                              │ ④ 检查 pg_hba.conf
  │                           │                              │ ⑤ 认证
  │◄─── AuthenticationOk ─────┼──────────────────────────────│
  │─── Query ─────────────────┼─────────────────────────────►│ ⑥ 解析→计划→执行
  │◄─── DataRow / ReadyForQuery┼──────────────────────────────│
  │                           │                              │
  │─── Terminate ─────────────┼─────────────────────────────►│ ⑦ 进程退出
  │                           │◄── SIGCHLD ──────────────────│
  │                           │ waitpid() 回收               │

关键点:监听套接字由所有子进程继承,但子进程必须立即关闭它(否则会在子进程退出时产生竞态)。这解释了为什么 connection refused 与 too many connections 是两种不同的错误——前者是 Postmaster 未监听,后者是它无法再 fork。

sql
-- 从内部确认「每连接一进程」:pg_backend_pid() 就是操作系统 PID
SELECT pg_backend_pid() AS os_pid,
       current_user,
       current_database(),
       inet_server_port() AS port;
text
 os_pid | current_user | current_database | port
--------+--------------+------------------+------
   1215 | postgres     | demo             | 5432
(1 row)
bash
# 在另一个终端核对:PID 1215 确实是我们的连接
ps -p 1215 -o pid,ppid,args
text
  PID  PPID COMMAND
 1215  1147 postgres: postgres demo [local] idle
sql
-- 连接数上限的三个约束:max_connections 与保留连接
SELECT current_setting('max_connections')::int                        AS max_conn,
       current_setting('superuser_reserved_connections')::int         AS reserved,
       count(*) FILTER (WHERE backend_type = 'client backend')        AS current_conn,
       current_setting('max_connections')::int
         - current_setting('superuser_reserved_connections')::int
         - count(*) FILTER (WHERE backend_type = 'client backend')    AS remaining;
text
 max_conn | reserved | current_conn | remaining
----------+----------+--------------+-----------
      100 |        3 |           12 |        85
(1 row)

进程间通信(IPC) ​

各进程拥有独立地址空间,通信必须借助外部机制。PostgreSQL 用到三种:

机制用途特点
共享内存(Shared Memory)数据交换:buffer、锁、事务状态、统计最快,但结构固定、需加锁保护
信号(Signal)控制通知:reload、shutdown、崩溃只能传一个整数,异步
闩锁(Latch)进程内与跨进程的等待唤醒自旋 + 事件循环,主要用于等待 I/O

Postmaster 与子进程之间的「控制通道」是一根自管道(self-pipe):信号处理函数只向管道写一个字节(信号在 handler 中只能异步安全操作),主循环再从管道读出并处理。这是避免在信号 handler 中调用非异步安全函数的经典手法。

信号对照表 ​

Postmaster 接收的信号与含义:

信号含义是否重启是否影响事务
SIGHUP重新加载配置文件否否
SIGTERM智能关闭(Smart Shutdown):等所有客户端断开后再停否否,等事务自然结束
SIGINT快速关闭(Fast Shutdown):回滚所有活跃事务并立即退出是(退出)是,事务被回滚
SIGQUIT立即关闭(Immediate Shutdown):不清理,下次启动做崩溃恢复是(异常退出)是,可能导致不一致
SIGUSR1通知子进程「共享内存状态变化,请重新读取」否否
SIGUSR2通知子进程「WAL 状态变化」否否
SIGCHLD子进程状态变化(崩溃或退出)视情况视情况
bash
# 用 pg_ctl 触发不同的关闭方式(不要手工 kill -9)
pg_ctl -D /var/lib/postgresql/18/main stop -m smart   # 智能:等连接结束
pg_ctl -D /var/lib/postgresql/18/main stop -m fast    # 快速:回滚并退出(推荐)
pg_ctl -D /var/lib/postgresql/18/main stop -m immediate  # 立即:下次启动需恢复

# reload 配置(等价于 SIGHUP)
pg_ctl -D /var/lib/postgresql/18/main reload

SIGKILL 与 SIGQUIT

kill -9(SIGKILL)无法被进程捕获,Postmaster 会在下次启动时执行崩溃恢复,虽然数据不会丢(WAL 保证),但会带来一次完整的恢复过程。而 SIGQUIT 给后端的语义是「立即自杀」,用于 Postmaster 判定共享内存已被污染时强制全体退出。日常运维只用 pg_ctl stop -m fast 或 systemctl stop。

后端进程数量与内存的关系 ​

每个后端进程(client backend)的基础内存开销包括:

内存区域典型大小说明
进程自身~5MB可执行代码 + 基础数据结构
catalog cache数 MB系统目录缓存,随访问到的目录数增长
work_mem每个排序/哈希节点默认 4MB,可能同时有多个
temp_buffers默认 8MB临时表缓存
连接相关缓冲数十 KB协议缓冲
sql
-- 粗算:连接数 × 单连接基础内存,评估实例内存是否够用
SELECT count(*) FILTER (WHERE backend_type = 'client backend') AS conns,
       pg_size_pretty(
         (count(*) FILTER (WHERE backend_type = 'client backend'))::bigint * 5 * 1024 * 1024
       ) AS rough_base_memory
FROM pg_stat_activity;
text
 conns | rough_base_memory
-------+-------------------
    12 | 60 MB
(1 row)

这只是粗略下限。真实占用取决于 work_mem、复杂查询的排序哈希节点数量、以及 catalog cache 的命中范围。用 pg_backend_memory_contexts 可以看单个会话的内存明细。

详细说明 ​

用 pg_stat_activity 观察进程全景 ​

pg_stat_activity 是观察进程状态的最主要视图。18 中的关键列:

列名含义排障价值
pid进程 ID与操作系统 PID 一致,可直接 kill 定位
backend_type进程角色区分辅助进程与客户端连接
state连接状态(见下表)判断「空闲占坑」还是「正在干活」
wait_event_type / wait_event等待类型与等待事件定位瓶颈最关键的列
query当前或最近一条 SQL注意:idle 时显示的是上一条
state_change状态变更时间算 idle 时长,判断连接是否泄漏
backend_start进程启动时间判断连接池是否复用连接
xact_start事务开始时间检测长事务的第一手线索
query_start当前查询开始时间算查询执行时长
client_addr / client_port客户端来源定位是哪个应用/机器
sql
-- 连接状态 state 的取值与含义
SELECT unnest(ARRAY[
  'active         当前正在执行查询',
  'idle           空闲,等待客户端下发新命令(正常)',
  'idle in transaction  在事务中空闲(危险,持有锁与快照)',
  'idle in transaction (aborted)  事务已出错但未回滚(危险)',
  'fastpath function call  正在执行 fast-path 函数',
  'disabled       跟踪已被关闭'
]) AS state_meanings;
state含义风险
active正在执行查询—
idle空闲等待新命令连接泄漏时表现为大量长时间 idle
idle in transaction开启了事务但未提交/回滚阻塞 VACUUM、持有锁、快照不释放
idle in transaction (aborted)事务报错后未回滚同样危险,且应用可能已卡死
fastpath function call执行 fast-path 函数少见
disabled该会话的统计跟踪已关闭—
sql
-- 综合巡检:按「是否值得警惕」排序,列出问题最严重的连接
SELECT pid,
       usename,
       datname,
       state,
       wait_event_type || '/' || coalesce(wait_event, '-')      AS wait,
       now() - xact_start                                        AS xact_age,
       now() - state_change                                      AS state_age,
       left(regexp_replace(coalesce(query, ''), '\s+', ' ', 'g'), 60) AS query_short
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY (state = 'idle in transaction') DESC NULLS LAST,
         xact_start ASC NULLS LAST;
text
  pid | usename  | datname |        state        |     wait      |  xact_age  | state_age  | query_short
------+----------+---------+---------------------+---------------+------------+------------+----------------------
 1301 | app_user | demo    | idle in transaction | ClientRead    | 00:12:44   | 00:12:41   | UPDATE orders SET ...
 1215 | postgres | demo    | active              | IO/DataFileRead| 00:00:02  | 00:00:02   | SELECT count(*) FROM ...
 1178 | postgres | demo    | idle                | ClientRead    |            | 00:31:02   | SELECT pg_sleep(60);
(3 rows)

等待事件:定位瓶颈的钥匙 ​

wait_event_type 表示等待的大类,wait_event 表示具体原因。生产排障几乎全靠这两列:

wait_event_type典型 wait_event含义常见诱因
LWLockBufferContent、WALWrite等待轻量锁buffer 竞争、WAL 刷盘瓶颈
Locktransactionid、tuple、relation等待重型锁长事务、外键未建索引、DDL 阻塞
IODataFileRead、WALWrite等待磁盘 I/O缓存不足、磁盘慢
BufferPinBufferPin等待 buffer 钉住释放VACUUM 与查询竞争
ClientClientRead、ClientWrite等待客户端收发数据应用处理慢、网络慢
IPCWalSync、BgWorkerShutdown进程间等待提交等待 WAL 落盘
TimeoutVacuumDelay、PgSleep主动等待autovacuum 限速、显式 sleep
`` (空)—无等待(正常空闲或纯 CPU 执行)—
sql
-- 当前等待事件分布:一眼看出瓶颈在锁、I/O 还是客户端
SELECT coalesce(wait_event_type, 'CPU/Running') AS wait_type,
       coalesce(wait_event, '-')                AS wait_event,
       count(*)                                 AS sessions
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1, 2
ORDER BY sessions DESC;
text
   wait_type   |   wait_event   | sessions
---------------+----------------+----------
 Client        | ClientRead     |        8
 Lock          | transactionid  |        2
 IO            | DataFileRead   |        1
 LWLock        | WALWrite       |        1
(4 rows)

锁等待的解读

wait_event_type = 'Lock' 且 wait_event = 'transactionid' 意味着「等待另一个事务结束」。这时用 pg_blocking_pids(pid) 可以直接找出阻塞者:

sql
SELECT pid,
       pg_blocking_pids(pid) AS blocked_by,
       state,
       left(query, 60) AS query
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;

后端进程的生命周期 ​

一个后端进程从创建到销毁的完整阶段:

阶段触发状态(pg_stat_activity)
1. 派生Postmaster fork()尚未进入记录(无 state)
2. 初始化读取共享内存、初始化本地内存无 state
3. 认证读 pg_hba.conf、校验口令/证书无 state
4. 就绪发送 ReadyForQueryidle
5. 执行查询收到 Query 消息active(或等待事件)
6. 事务中空闲开启事务但未结束idle in transaction
7. 退出客户端断开或 pg_terminate_backend()记录消失
sql
-- 观察 backend_start 与 state_change 的差值,判断连接是否被复用
SELECT pid,
       backend_start,
       state_change,
       state,
       now() - backend_start  AS conn_lifetime,
       coalesce(application_name, '-') AS app_name
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;
text
 pid  |         backend_start         |          state_change         | state  | conn_lifetime | app_name
------+-------------------------------+-------------------------------+--------+---------------+----------
 1178 | 2026-09-22 09:20:11.482931+08 | 2026-09-22 09:22:45.117204+08 | idle   | 00:31:02      | psql
 1215 | 2026-09-22 09:31:44.902117+08 | 2026-09-22 09:38:02.551200+08 | active | 00:19:29      | pgbench
 1301 | 2026-09-22 09:25:03.117204+08 | 2026-09-22 09:25:03.200117+08 | idle in transaction | 00:26:10 | app-server
(3 rows)

为什么必须用连接池 ​

「每连接一进程」带来的成本:

成本项数值(经验值)影响
fork() 延迟0.1~1ms高 QPS 短连接场景下成为瓶颈
单连接基础内存5~10MB(不含 work_mem)连接数直接吃内存
上下文切换每连接一个可调度实体连接数过高时 CPU 花在切换上
锁竞争更多进程争抢 LWLockbuffer / WAL 锁竞争加剧

因此 PostgreSQL 社区的共识是:数据库侧连接数应控制在 100~300 的区间(与 CPU 核数相关),超出部分由连接池承担。pgbouncer 的 transaction 池模式可以让「1000 个应用连接」映射到「20 个数据库连接」。

text
应用 1000 连接 ──► PgBouncer(transaction 模式) ──► PostgreSQL 20 个后端进程

连接池的代价

transaction 池模式下,同一个数据库连接会在不同应用请求间复用,因此会话级状态会泄漏:SET、PREPARE、临时表、游标、LISTEN/NOTIFY、会话级 advisory lock 都会在使用者之间串味。用 session 池模式可以避免,但复用率下降。应用若依赖会话状态,务必选择 session 模式。

辅助进程的启动与退出 ​

辅助进程在实例启动时由 Postmaster 一次性 fork,并在整个实例生命周期内常驻。它们之间也有依赖顺序:

text
Postmaster 启动
    ├─► 初始化共享内存(shared_buffers / WAL buffer / ProcArray / 锁表)
    ├─► 执行崩溃恢复(若上次非正常关闭)
    ├─► fork checkpointer
    ├─► fork background writer
    ├─► fork walwriter
    ├─► fork archiver(若 archive_mode = on)
    ├─► fork autovacuum launcher
    ├─► fork logical replication launcher
    └─► 进入监听循环,准备 accept 客户端连接
sql
-- 观察各辅助进程的启动时间,确认它们是否被重启过
SELECT backend_type,
       pid,
       backend_start,
       now() - backend_start AS uptime
FROM pg_stat_activity
WHERE backend_type <> 'client backend'
ORDER BY backend_start;
text
        backend_type        | pid  |         backend_start         |  uptime
----------------------------+------+-------------------------------+----------
 checkpointer               | 1149 | 2026-09-22 09:12:01.117204+08 | 00:39:12
 background writer          | 1150 | 2026-09-22 09:12:01.120117+08 | 00:39:12
 walwriter                  | 1152 | 2026-09-22 09:12:01.135882+08 | 00:39:12
 autovacuum launcher        | 1153 | 2026-09-22 09:12:01.148201+08 | 00:39:12
 logical replication launcher | 1154 | 2026-09-22 09:12:01.150117+08 | 00:39:12
(5 rows)

从 uptime 发现异常重启

如果 uptime 明显小于实例的启动时间(对比 pg_postmaster_start_time()),说明该辅助进程曾异常退出并被 Postmaster 重建——这通常意味着它崩溃过,值得查日志。

sql
SELECT pg_postmaster_start_time() AS instance_start,
       now() - pg_postmaster_start_time() AS instance_uptime;

实战示例 ​

示例一:绘制实例的进程全景图 ​

sql
-- 一次查询输出进程分类统计与资源占用
SELECT backend_type,
       count(*)                                            AS cnt,
       count(*) FILTER (WHERE state = 'active')            AS active,
       count(*) FILTER (WHERE state LIKE 'idle in transaction%') AS idle_in_xact,
       min(backend_start)                                  AS earliest_start
FROM pg_stat_activity
GROUP BY backend_type
ORDER BY cnt DESC;
text
          backend_type          | cnt | active | idle_in_xact |      earliest_start
--------------------------------+-----+--------+--------------+---------------------------
 client backend                 |  12 |      3 |            1 | 2026-09-22 09:20:11.48+08
 checkpointer                   |   1 |      0 |            0 | 2026-09-22 09:12:01.11+08
 background writer              |   1 |      0 |            0 | 2026-09-22 09:12:01.12+08
 walwriter                      |   1 |      0 |            0 | 2026-09-22 09:12:01.13+08
 autovacuum launcher            |   1 |      0 |            0 | 2026-09-22 09:12:01.14+08
 logical replication launcher   |   1 |      0 |            0 | 2026-09-22 09:12:01.15+08
(6 rows)
bash
# 与操作系统对照:统计所有 postgres 进程及其内存占用(RSS)
ps -eo pid,ppid,rss,args --sort=-rss | grep '[p]ostgres' | head -10
text
  PID  PPID   RSS COMMAND
 1301  1147 28452 postgres: app_user demo 10.0.1.5(51234) idle in transaction
 1215  1147 19884 postgres: postgres demo [local] SELECT
 1178  1147  9120 postgres: postgres demo [local] idle
 1147     1  6214 /usr/lib/postgresql/18/bin/postgres -D /var/lib/postgresql/18/main
 1149  1147  3412 postgres: checkpointer
 1150  1147  3388 postgres: background writer
 1152  1147  3320 postgres: walwriter
 1153  1147  3204 postgres: autovacuum launcher

RSS 单位是 KB。可以看到:4 个辅助进程的内存占用合计不到 15MB,而一个 idle in transaction 的客户端后端就占了约 28MB。这就是「连接数即内存」的直观体现。

示例二:定位并清理阻塞链 ​

sql
-- 第 1 步:找出正在等待锁的会话及其阻塞者
WITH blocked AS (
  SELECT pid,
         pg_blocking_pids(pid) AS blockers,
         state,
         wait_event,
         now() - xact_start   AS xact_age,
         left(query, 50)      AS query_short
  FROM pg_stat_activity
  WHERE backend_type = 'client backend'
)
SELECT b.pid                                        AS blocked_pid,
       array_to_string(b.blockers, ',')             AS blocking_pids,
       b.state,
       b.wait_event,
       b.xact_age,
       b.query_short
FROM blocked b
WHERE cardinality(b.blockers) > 0;
text
 blocked_pid | blocking_pids |        state         |   wait_event   | xact_age  |    query_short
-------------+---------------+----------------------+----------------+-----------+--------------------
        1240 | 1301          | active               | transactionid  | 00:00:12  | UPDATE orders SET ...
(1 row)
sql
-- 第 2 步:查看阻塞者 1301 的状态(典型的 idle in transaction)
SELECT pid, state, now() - xact_start AS xact_age, left(query, 60) AS last_query
FROM pg_stat_activity
WHERE pid = 1301;
text
  pid |        state        | xact_age |            last_query
------+---------------------+----------+----------------------------------
 1301 | idle in transaction | 00:12:44 | UPDATE orders SET status = 'paid'
(1 row)
sql
-- 第 3 步:先尝试温和取消(让应用自己处理错误)
SELECT pg_cancel_backend(1301);
text
 pg_cancel_backend
-------------------
 t
(1 row)
sql
-- 第 4 步:若取消无效(应用不响应或已卡死),再终止该连接
SELECT pg_terminate_backend(1301);

-- 第 5 步:确认阻塞已解除
SELECT count(*) AS still_blocked
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;
text
 still_blocked
---------------
             0
(1 row)

预防 idle in transaction

问题的根因是应用「开了事务却去干别的事」。两道防线:

  1. idle_in_transaction_session_timeout:超时自动终止事务。设 60s 或更低。
  2. statement_timeout / lock_timeout:为单条语句与等锁设置上限。
sql
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60s';
ALTER SYSTEM SET statement_timeout = '30s';
ALTER SYSTEM SET lock_timeout = '5s';
SELECT pg_reload_conf();

示例三:验证信号的实际效果 ​

bash
# 场景:模拟一次「共享内存被污染」后的自动重启
# 1) 记录当前主进程 PID
MASTER=$(head -1 /var/lib/postgresql/18/main/postmaster.pid)
echo "主进程 PID: $MASTER"

# 2) 杀掉一个后端进程的父级检测路径:用 SIGQUIT 让一个后端立即退出
psql -d demo -c "SELECT pg_backend_pid();"   # 假设返回 1215
kill -QUIT 1215

# 3) 观察日志:Postmaster 会把这种情况视为崩溃信号,重启整个实例
tail -20 /var/log/postgresql/postgresql-18-main.log
text
LOG:  server process (PID 1215) was terminated by signal 3: Quit
LOG:  terminating any other active server processes
WARNING:  terminating connection because of crash of another server process
DETAIL:  The postmaster has commanded this server process to roll back the current
         transaction and exit, because another server process exited abnormally
         and possibly corrupted shared memory.
LOG:  all server processes terminated; reinitializing
LOG:  database system was interrupted; last known up at 2026-09-22 09:40:12 CST
LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  redo starts at 0/16B3748
LOG:  consistent recovery state reached at 0/16C0A28
LOG:  database system is ready to accept connections
sql
-- 4) 恢复后核对:实例的"启动时间"已更新,但数据完好
SELECT pg_postmaster_start_time() AS restarted_at,
       (SELECT count(*) FROM pg_stat_activity) AS backends;
text
         restarted_at
-------------------------------
 2026-09-22 09:41:03.117204+08
(1 row)

不要在生产模拟

上面这段仅用于理解机制。生产环境千万不要手工 kill -QUIT 后端进程——虽然 WAL 能保证数据不丢,但会中断所有连接并触发全量恢复。

注意事项 ​

  • 「集群」在 PostgreSQL 文档中指一个数据目录,不是多机集群,读文档时务必区分。
  • 不要用 kill -9:会导致共享内存状态不一致,Postmaster 触发全体重启与崩溃恢复。终止查询用 pg_cancel_backend(),终止连接用 pg_terminate_backend()。
  • 手工删除 postmaster.pid 极其危险:若实例实际在运行,会造成双实例写同一份数据文件 → 数据损坏。删除前必须确认没有 postgres 进程。
  • SIGTERM 是「等待」而 SIGINT(fast)是「回滚退出」:pg_ctl stop -m smart 在长事务存在时可能长时间挂住,生产建议用 -m fast。
  • idle 与 idle in transaction 是两个完全不同的概念:前者无害,后者会阻塞 VACUUM 并持有锁。监控必须分开统计。
  • 连接数受双重限制:max_connections 减去 superuser_reserved_connections 才是普通用户可用的额度。
  • pg_stat_activity.query 在 idle 时显示的是最后一条 SQL:不要误判为「正在执行」,要看 state。
  • 权限限制:非超级用户只能看到自己连接的 query / client_addr 等敏感列(其他连接显示为 <insufficient privilege>)。监控账号需要 pg_monitor 角色。
  • 辅助进程不可单独重启:它们由 Postmaster 统一管理,异常退出会被重建;若反复崩溃,Postmaster 会在达到阈值后停止实例。
  • 并行查询会临时创建 parallel worker 进程:这是正常现象,不要当成连接泄漏。
sql
-- 不推荐:用超高的 max_connections 硬扛连接压力
-- ALTER SYSTEM SET max_connections = 2000;   -- 不推荐
-- 推荐:连接池 + 合理的数据库侧连接上限

最佳实践 ​

  • 生产必须部署连接池:用 PgBouncer(transaction 模式)把应用连接数与数据库后端数解耦,数据库侧连接数控制在 CPU 核数的 2~4 倍。
  • 设置三道超时防线:statement_timeout、lock_timeout、idle_in_transaction_session_timeout,把「卡死的会话」变成「自动失败的语句」。
  • 给监控账号授 pg_monitor:否则看不到其他会话的 query 与 client_addr,排查会失去最关键的信息。
  • 把等待事件纳入监控:对 wait_event_type = 'Lock' 且持续超过阈值的会话告警,能提前发现锁雪崩。
  • 输出进程分类看板:按 backend_type 统计连接数与状态分布,而不是只看「总连接数」这一个数字。
  • 关闭时统一用 -m fast:smart 容易被长事务卡住,immediate 会引发恢复,fast 是运维默认选择。
  • 不要在生产手工杀进程:所有终止操作走 pg_cancel_backend / pg_terminate_backend,并记入变更日志。
  • 长事务专项巡检:每天检查 xact_start 超过 10 分钟的会话,逐个确认是否为应用 bug(未提交的事务是 VACUUM 与膨胀的最大元凶)。
  • 辅助进程的异常重启要查日志:若某个辅助进程的 uptime 与实例不一致,说明它崩溃过,需从日志定位原因。
  • 升级到 18 后更新进程名对照表:监控脚本里对 postgres: writer process 之类的旧名称匹配应改为 backend_type。

参考链接 ​