Skip to content

Backend 后端进程(Backend Process / 会话生命周期) ​

后端进程(Backend Process)是客户端与数据库之间的唯一执行者:每个连接对应一个后端进程,SQL 的解析、计划、执行、事务管理、返回结果全部由它完成。本节讲清后端进程的创建与初始化、内存与状态、生命周期各阶段、等待事件诊断,以及为什么 PostgreSQL 必须配连接池。

前置知识

阅读本节前,建议先了解:Postmaster 主进程(Postmaster / 监听与派生)

基础概念 ​

「每个连接一个进程」是 PostgreSQL 与 MySQL(线程模型)、Oracle(服务端进程模型)最本质的架构差异之一。

术语英文名说明
后端进程Backend Process服务单个客户端连接的进程,backend_type = 'client backend'
会话Session一个连接从建立到断开的完整周期
简单查询协议Simple Query Protocol一次发送一条 SQL 文本,可含多条语句
扩展查询协议Extended Query ProtocolParse/Bind/Describe/Execute 分步,支持参数与游标
PortalPortal扩展协议下执行计划的运行实例,游标(CURSOR)的底层
并行 workerParallel Worker为单个查询派生的辅助进程,backend_type = 'parallel worker'
等待事件Wait Event后端进程此刻在等什么(锁、IO、WAL、客户端……)
本地内存Local Memory后端私有的 work_mem、catalog cache、temp_buffers 等
事务状态Transaction Stateidle / active / idle in transaction / idle in transaction (aborted)

idle in transaction 是必须消灭的状态

一个后端进程处于 idle in transaction 表示:它开启了事务(BEGIN)、执行过操作、但既没有提交也没有回滚,也没有在执行任何 SQL。此时它持有的锁与快照全部保留:VACUUM 无法回收比它更老的行版本、DDL 可能被阻塞、pg_xact 无法推进。放任不管最终会导致表膨胀与 XID 环绕告警。必须设置 idle_in_transaction_session_timeout。

后端进程的三类内存 ​

后端进程的内存由三部分组成,理解它们就能理解连接数为什么不能太多:

text
┌───────────────────────────────────────────────────────────┐
│ ① 共享内存(所有进程共享,后端只读/受控写)                 │
│    shared_buffers / wal_buffers / 锁表 / ProcArray         │
│    → 不随连接数线性增长                                    │
├───────────────────────────────────────────────────────────┤
│ ② 后端固定开销(每个后端进程独有,约 5~10MB 起)            │
│    进程栈、执行器状态、relcache/syscache 初始条目            │
│    → 随连接数线性增长,是连接数的「地板成本」                │
├───────────────────────────────────────────────────────────┤
│ ③ 后端动态内存(按操作分配,用完释放)                      │
│    work_mem(每个排序/哈希节点)/ temp_buffers /            │
│    catalog cache(随访问过的目录对象数量增长,通常不归还)    │
│    → 峰值随并发查询数与查询复杂度放大                       │
└───────────────────────────────────────────────────────────┘
sql
-- 查看当前后端进程的内存上下文分布(只对当前会话有效)
SELECT name,
       pg_size_pretty(total_bytes) AS total,
       pg_size_pretty(used_bytes)  AS used
FROM pg_backend_memory_contexts
ORDER BY total_bytes DESC
LIMIT 12;
text
        name         |  total  |  used
---------------------+---------+---------
 TopPortalContext    | 4096 kB | 4096 kB
 CacheMemoryContext  | 1976 kB | 1976 kB   -- catalog cache,长会话会持续增长
 PortalContext        | 1048 kB | 1048 kB
 MessageContext      |  524 kB |  524 kB
 ExecutorState       |  116 kB |  116 kB
 TopMemoryContext    |   82 kB |   82 kB
(6 rows)

CacheMemoryContext 是长连接最需要关注的一项:它缓存表定义、列类型、函数签名等元数据,一旦装载就很少释放。访问过大量不同表的会话(如报表、多租户混合查询)会让它膨胀到几十甚至上百 MB。

核心机制 ​

后端进程的完整生命周期 ​

text
① 连接建立
   Postmaster accept → fork() → 后端进程诞生
        │
        ▼
② 认证阶段(读 pg_hba.conf,选择认证方法)
   ├─ 失败 → 记录日志 → 关闭连接 → 进程退出
   └─ 成功 → 继续
        │
        ▼
③ 初始化阶段
   ├─ 初始化 Backend 状态结构
   ├─ 建立与目标数据库的连接(PgDatabase)
   ├─ 初始化 catalog cache(relcache/syscache)
   ├─ 应用 ALTER ROLE / ALTER DATABASE 的参数覆盖
   └─ 进入主循环
        │
        ▼
④ 主循环:等待客户端消息
   ├─ 收到 SQL → 解析 → 计划 → 执行 → 返回结果 → 回到等待
   ├─ 事务开始/提交/回滚 → 更新 pg_xact、写 WAL
   ├─ 空闲(idle)→ 等待
   └─ 客户端断开 / 被终止 / 空闲超时 → 退出循环
        │
        ▼
⑤ 退出清理
   ├─ 回滚未提交事务
   ├─ 释放锁、清理复制槽保留位、取消监听
   ├─ 释放共享内存中的 ProcArray 槽位
   ├─ 最后清理临时文件(base/pgsql_tmp/...)
   └─ exit() → Postmaster 收到 SIGCHLD → 回收
sql
-- 完整观察一个会话的生命周期关键时间点
SELECT pid,
       backend_start,                                   -- 进程创建时间
       xact_start,                                      -- 当前事务开始时间
       query_start,                                     -- 当前 SQL 开始时间
       state_change,                                    -- 状态最后一次变化时间
       now() - backend_start AS session_age,
       state,
       backend_type
FROM pg_stat_activity
WHERE pid = pg_backend_pid();
text
  pid  |        backend_start         |   xact_start   | state_change | session_age | state  | backend_type
-------+------------------------------+----------------+--------------+-------------+--------+---------------
 1301  | 2026-09-22 10:15:03.221+08   |                | 10:15:03.221 | 00:03:12.4  | active | client backend
(1 row)

状态机与各状态的含义 ​

pg_stat_activity.state 只有五种取值,每一种的运维含义完全不同:

state 取值含义危害处理建议
active正在执行 SQL正常关注长耗时与等待事件
idle连接空闲,等待客户端发指令占用连接名额用连接池;idle_session_timeout 兜底
idle in transaction事务已开,但后端没在执行 SQL持有锁与快照,阻塞 VACUUMidle_in_transaction_session_timeout 强制回收
idle in transaction (aborted)事务中出过错但未回滚同上有害,且客户端逻辑有 bug必须回滚或断开
fastpath function call执行不可中断的函数(如 dblink)无法用 pg_cancel_backend 中断谨慎使用此类函数
sql
-- 按状态汇总连接,快速发现异常状态
SELECT state,
       count(*) AS conns,
       max(now() - state_change) AS longest_in_state
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state
ORDER BY conns DESC;
text
        state        | conns | longest_in_state
---------------------+-------+------------------
 idle                |   118 | 02:41:07.882
 active              |    19 | 00:00:12.331
 idle in transaction |     5 | 00:31:44.501   -- 31 分钟未提交,极其危险
(3 rows)
sql
-- 直接揪出危险的 idle in transaction 会话
SELECT pid,
       usename,
       application_name,
       client_addr,
       now() - xact_start AS xact_age,
       now() - state_change AS idle_age,
       left(query, 60) AS last_query
FROM pg_stat_activity
WHERE state LIKE 'idle in transaction%'
ORDER BY xact_age DESC;
text
  pid  | usename  | application_name | client_addr |  xact_age   | idle_age  |  last_query
-------+----------+------------------+-------------+-------------+-----------+----------------
 1402  | app_user | app-server-01   | 10.0.0.11   | 00:31:44.50 | 00:31:40  | UPDATE t_order ...

超时参数的正确配置顺序

这三个超时参数分别防三种问题,建议全部显式设置:

ini
# postgresql.conf
# 空闲连接(无事务)超时:防止连接泄漏耗尽 max_connections
idle_session_timeout = '15min'

# 事务中空闲超时:防止长事务阻塞 VACUUM 与 DDL(PG 9.6+)
idle_in_transaction_session_timeout = '60s'

# 单条语句超时:防止失控查询打满 IO(PG 9.3+)
statement_timeout = '30s'

# 事务总时长超时:防止大事务撑爆 WAL(PG 12+)
transaction_timeout = '10min'

transaction_timeout 是 PostgreSQL 17 新增的参数,它覆盖整个事务(含 idle in transaction 时间),比 statement_timeout 更能约束「分批提交的长事务」。

等待事件:后端进程此刻在等什么 ​

等待事件是诊断「SQL 为什么慢」的最后一环。它由 wait_event_type(大类)与 wait_event(具体事件)两个字段组成。

wait_event_type常见 wait_event含义与处理
LWLockBufferContent、WALWrite轻量锁争用,通常是热点页或 WAL 写瓶颈
Locktransactionid、relation、tuple等事务/表/行锁,用 pg_blocking_pids() 找阻塞者
IODataFileRead、DataFileWrite真实磁盘 I/O,检查是否缓存命中率不足
ClientClientRead、ClientWrite等客户端收发数据,通常是应用侧慢
IPCParallelWaitForWorkers、SyncRep等并行 worker 或同步复制确认
BufferPinBufferPin等缓冲区的 pin 释放
TimeoutBaseBackup、CheckpointWriteDelay主动的等待(节流、重试)
Extensionextension扩展内部等待
ActivityAutovacuumMain、CheckpointerMain辅助进程的空闲等待(正常)
sql
-- 按等待事件归类当前活跃会话:一眼看出瓶颈在哪一层
SELECT wait_event_type,
       wait_event,
       count(*) AS sessions,
       max(now() - query_start) AS longest
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 |    longest
-----------------+-------------------+----------+---------------
 Lock            | transactionid     |        7 | 00:01:52.331
 IO              | DataFileRead      |        4 | 00:00:08.221
 Client          | ClientRead        |        2 | 00:00:00.914
(3 rows)

注意 wait_event_type 为 NULL 表示正在使用 CPU 计算(既不等锁也不等 I/O),这是健康的「真忙碌」状态;若 active 且等待事件为空且耗时很长,说明是 CPU 密集(大排序、复杂函数计算)。

sql
-- 定位锁阻塞链:谁在等谁
SELECT pid,
       pg_blocking_pids(pid) AS blocked_by,
       state,
       now() - query_start AS query_age,
       left(query, 50) AS query_preview
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0
ORDER BY query_age DESC;
text
  pid  | blocked_by | state  |  query_age   |   query_preview
-------+------------+--------+--------------+-------------------
 18823 | {1402}     | active | 00:01:52.331 | UPDATE t_order SET status = ...
 18850 | {1402}     | active | 00:01:48.102 | ALTER TABLE t_order ADD COLUMN ...
(2 rows)
sql
-- 顺藤摸瓜查看阻塞者本身的信息
SELECT pid, state, xact_start, now() - xact_start AS xact_age, left(query, 60)
FROM pg_stat_activity
WHERE pid = 1402;
text
  pid  |        state        |   xact_start   |  xact_age   |              left
-------+---------------------+----------------+-------------+--------------------------------
 1402  | idle in transaction | 10:14:10.021   | 00:34:02.11 | UPDATE t_order SET status = ...
(1 row)

结论:一个 34 分钟未提交的 idle in transaction 会话,正阻塞两条 t_order 上的写操作。处理方式是联系应用侧提交/回滚,或 pg_terminate_backend(1402)。

简单查询协议 vs 扩展查询协议 ​

后端进程对客户端请求的处理方式取决于使用的协议,这直接影响性能与安全性。

维度简单查询协议(Simple)扩展查询协议(Extended)
发送方式一次性发 SQL 文本Parse → Bind → Describe → Execute → Sync
参数化无,需拼接字符串有 $1、$2,天然防注入
计划缓存无有(第 6 次起可能用通用计划)
多语句支持(分号分隔,隐式事务)单次 Execute 一条
二进制传输不支持支持(减少文本转换开销)
典型使用者psql 交互、简单脚本JDBC/psycopg 等驱动默认
sql
-- psql 中观察客户端编码与协议相关信息
SELECT name, setting
FROM pg_settings
WHERE name IN ('client_encoding', 'standard_conforming_strings', 'extra_float_digits')
ORDER BY name;
text
            name            | setting
----------------------------+---------
 client_encoding            | UTF8
 extra_float_digits         | 1
 standard_conforming_strings | on
bash
# ── 用 psql 观察扩展协议下预处理语句的复用 ──
psql -h localhost -U app_user -d app_prod -c "\prepared"
text
       name       |               statement                | parameter_types | generic_plans | custom_plans
------------------+----------------------------------------+-----------------+---------------+--------------
 find_user_by_id  | SELECT id, name FROM t_user WHERE ...  | {integer}       |             0 |            7

详细说明 ​

后端进程与并行查询 ​

一个查询也可能消耗多个进程:当计划器决定并行执行时,会向 Postmaster 申请派生 并行 worker。它们不是独立的客户端连接,但会各自占用内存与 CPU。

sql
-- 查看并行相关配置
SELECT name, setting
FROM pg_settings
WHERE name IN ('max_parallel_workers', 'max_parallel_workers_per_gather',
               'max_worker_processes', 'parallel_setup_cost', 'parallel_tuple_cost',
               'min_parallel_table_scan_size')
ORDER BY name;
text
              name              | setting
--------------------------------+---------
 max_parallel_workers           | 8
 max_parallel_workers_per_gather | 2
 max_worker_processes           | 8
 min_parallel_table_scan_size   | 8MB
 parallel_setup_cost            | 1000
 parallel_tuple_cost            | 0.1
(6 rows)
sql
-- 观察并行查询:Gather 节点下的 workers planned / launched
EXPLAIN (ANALYZE, TIMING off)
SELECT count(*) FROM t_order_demo WHERE amount > 5000;
text
                                     QUERY PLAN
-------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=12485.33..12485.34 rows=1 width=8) (actual rows=1 loops=1)
   ->  Gather  (cost=12485.11..12485.32 rows=2 width=8) (actual rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=11485.00..11485.01 rows=1 width=8) (actual rows=1 loops=3)
               ->  Parallel Seq Scan on t_order_demo  (cost=0.00..11485.00 rows=20783 width=0) (actual rows=16642 loops=3)
                     Filter: (amount > 5000::numeric)
sql
-- 并行 worker 也出现在 pg_stat_activity 中,可以逐个追踪
SELECT pid, backend_type, state, wait_event_type, wait_event, left(query, 40)
FROM pg_stat_activity
WHERE backend_type IN ('parallel worker', 'client backend')
  AND state = 'active'
ORDER BY backend_type;
text
  pid  |   backend_type   | state  | wait_event_type |   wait_event   |                left
-------+------------------+--------+-----------------+----------------+----------------------------------
 1901  | client backend   | active | IPC             | ParallelWaitForWorkers | SELECT count(*) FROM t_order_demo
 1902  | parallel worker  | active | IO              | DataFileRead   | SELECT count(*) FROM t_order_demo
 1903  | parallel worker  | active | IO              | DataFileRead   | SELECT count(*) FROM t_order_demo

并行 worker 是「共享资源」

max_worker_processes 是实例级的总池,max_parallel_workers 是并行查询能用的子集,max_parallel_workers_per_gather 是单个 Gather 节点能申请的 worker 数。多个并发查询会争抢这个池,池满时后续查询会退化为串行而不是报错。压测时若发现「加了并行参数但没提速」,先查 worker 是否已被占满。

后端进程的终止方式 ​

三种终止方式的影响面完全不同:

方式函数/命令效果事务状态
取消当前查询pg_cancel_backend(pid)中断正在执行的 SQL事务保留,可回滚或继续
终止整个会话pg_terminate_backend(pid)断开连接、进程退出事务被回滚
立即终止(不清理)无直接 SQL 接口走崩溃恢复风险—
sql
-- 优先使用 pg_cancel_backend:温和,只打断当前语句
SELECT pg_cancel_backend(18823);
text
 pg_cancel_backend
-------------------
 t
sql
-- 谨慎使用 pg_terminate_backend:会断开连接,应用需能重连
SELECT pg_terminate_backend(1402),
       (SELECT application_name FROM pg_stat_activity WHERE pid = 1402) AS app;
text
 pg_terminate_backend |      app
----------------------+----------------
 t                    | app-server-01
sql
-- 批量终止超时的 idle in transaction 会话(务必排除自己与关键任务)
SELECT pg_terminate_backend(pid) AS killed, pid, application_name
FROM pg_stat_activity
WHERE state LIKE 'idle in transaction%'
  AND now() - state_change > interval '5 minutes'
  AND pid <> pg_backend_pid();
text
 killed | pid  | application_name
--------+------+------------------
 t      | 1402 | app-server-01
(1 row)

权限与自我保护

  • pg_terminate_backend 需要超级用户或 pg_signal_backend 角色,且只能终止同角色的会话。
  • 批量终止前必须排除 pg_backend_pid() 自身,否则可能把自己干掉。
  • fastpath function call 状态的后端无法被取消(它在执行不可中断的函数),只能等它结束或直接 terminate。
sql
-- 授权运维账号具备终止其他会话的能力(而不给超级用户)
GRANT pg_signal_backend TO ops_user;

会话级参数与 ALTER ROLE 的覆盖机制 ​

后端进程初始化时会按「数据库 → 角色 → 会话」的优先级应用参数覆盖,这也是为什么同一个参数在不同会话里值可能不同。

优先级从低到高:

text
1. postgresql.conf / postgresql.auto.conf      (实例级)
        ↓ 被覆盖
2. ALTER DATABASE ... SET                      (数据库级)
        ↓ 被覆盖
3. ALTER ROLE ... SET(含 ALTER ROLE IN DATABASE)(角色级)
        ↓ 被覆盖
4. 连接串中的 options 参数 / PGOPTIONS 环境变量  (连接级)
        ↓ 被覆盖
5. 会话内 SET / set_config()                    (会话级)
sql
-- 为高频报表用户单独提高 work_mem,而不影响全局
ALTER ROLE report_user SET work_mem = '256MB';
ALTER ROLE report_user SET statement_timeout = '10min';
ALTER ROLE report_user IN DATABASE app_dw SET maintenance_work_mem = '1GB';
sql
-- 用 pg_settings.source 验证参数的最终来源
SELECT name,
       setting,
       source,
       sourceline  -- 若来自配置文件,显示行号;来自 ALTER 则为空
FROM pg_settings
WHERE name IN ('work_mem', 'maintenance_work_mem', 'statement_timeout');
text
        name         | setting |         source          | sourceline
---------------------+---------+-------------------------+-----------
 maintenance_work_mem | 1048576 | database                |
 statement_timeout    | 600000  | role                    |
 work_mem             | 262144  | role                    |
(3 rows)

source 的常见取值:default(内置默认)、configuration file(配置文件)、database(ALTER DATABASE)、role(ALTER ROLE)、session(SET)、client(连接串)。

实战示例 ​

示例 1:完整追踪一个会话的内存与状态 ​

sql
-- ── 第 1 步:建立会话并记录基础信息 ──
SELECT pg_backend_pid() AS my_pid,
       pg_postmaster_start_time() AS instance_start,
       current_database() AS db,
       current_user AS usr,
       current_setting('work_mem') AS work_mem;
text
 my_pid |       instance_start        |    db    |   usr    | work_mem
--------+-----------------------------+----------+----------+----------
   1901 | 2026-09-20 09:12:33.418+08  | app_prod | app_user | 4MB
(1 row)
sql
-- ── 第 2 步:执行一个需要大量本地内存的操作,观察内存上下文变化 ──
EXPLAIN (ANALYZE, TIMING off)
SELECT user_id, count(*), sum(amount)
FROM t_order_demo
GROUP BY user_id
HAVING sum(amount) > 100000;
text
                                     QUERY PLAN
----------------------------------------------------------------------------------
 GroupAggregate  (cost=3582.11..3832.11 rows=1000 width=44) (actual rows=3 loops=1)
   Filter: (sum(amount) > 100000::numeric)
   ->  Sort  (cost=3582.11..3632.11 rows=100000 width=14) (actual rows=100000 loops=1)
         Sort Key: user_id
         Sort Method: quicksort  Memory: 8721kB        ← 排序在内存中完成
         ->  Seq Scan on t_order_demo  (cost=0.00..2084.00 rows=100000 width=14) (actual rows=100000 loops=1)

Sort Method: quicksort Memory: 8721kB 说明排序没有落盘(落盘会显示 external merge Disk: xxxkB)。若 work_mem 小于所需,会变成外部排序,性能下降一个数量级。

sql
-- ── 第 3 步:查看此时后端进程的内存上下文排序 ──
SELECT name, pg_size_pretty(total_bytes) AS total, pg_size_pretty(used_bytes) AS used
FROM pg_backend_memory_contexts
ORDER BY total_bytes DESC
LIMIT 8;
text
        name        |  total  |  used
--------------------+---------+---------
 CacheMemoryContext | 2104 kB | 2104 kB
 TopMemoryContext   | 1024 kB | 1024 kB
 ExecutorState      |  892 kB |  892 kB   ← 执行器状态随查询规模增长
 TupleSort          |  128 kB |  128 kB
(4 rows)

示例 2:复现并解决连接泄漏 ​

sql
-- ── 第 1 步:先看连接现状 ──
SELECT count(*) AS total_conns,
       count(*) FILTER (WHERE state = 'idle') AS idle_conns,
       count(*) FILTER (WHERE state = 'active') AS active_conns,
       current_setting('max_connections')::int AS max_conns
FROM pg_stat_activity
WHERE backend_type = 'client backend';
text
 total_conns | idle_conns | active_conns | max_conns
-------------+------------+--------------+-----------
         196 |        178 |           18 |       200

196/200,非常危急。178 个空闲连接是典型泄漏特征。

sql
-- ── 第 2 步:按应用与来源聚合,定位泄漏的客户端 ──
SELECT application_name,
       client_addr,
       count(*) AS conns,
       min(backend_start) AS oldest_conn,
       max(now() - state_change) AS longest_idle
FROM pg_stat_activity
WHERE backend_type = 'client backend'
  AND state = 'idle'
GROUP BY application_name, client_addr
ORDER BY conns DESC;
text
 application_name | client_addr | conns |         oldest_conn         |  longest_idle
------------------+-------------+-------+------------------------------+---------------
 app-server-03   | 10.0.0.15   |   121 | 2026-09-22 08:01:22.113+08  | 02:41:07.882
 app-server-01   | 10.0.0.11   |    57 | 2026-09-22 09:33:41.882+08  | 00:42:11.220
(2 rows)

app-server-03 有 121 个空闲连接,且最老的从早上 8 点就在——这不是忙,是泄漏(连接池配置错误或未归还连接)。

ini
# postgresql.conf —— 兜底防护,即使应用没修好也不会拖垮实例
idle_session_timeout = '10min'                 # 空闲连接 10 分钟自动断开
idle_in_transaction_session_timeout = '60s'    # 事务中空闲 1 分钟强制回滚
sql
-- ── 第 3 步:立即止血(先用 cancel,无效再 terminate) ──
SELECT pg_terminate_backend(pid) AS killed,
       application_name,
       now() - state_change AS idle_for
FROM pg_stat_activity
WHERE backend_type = 'client backend'
  AND state = 'idle'
  AND client_addr = '10.0.0.15'
  AND now() - state_change > interval '30 minutes'
  AND pid <> pg_backend_pid();
text
 killed | application_name |   idle_for
--------+------------------+---------------
 t      | app-server-03   | 02:41:07.882
 t      | app-server-03   | 02:38:50.221
 ...

示例 3:用等待事件定位「SQL 慢在哪一层」 ​

sql
-- ── 一条 SQL 跑了很久,先看它在等什么 ──
SELECT pid,
       state,
       wait_event_type AS wait_type,
       wait_event,
       now() - query_start AS elapsed,
       left(query, 60) AS query
FROM pg_stat_activity
WHERE state = 'active'
  AND now() - query_start > interval '5 seconds'
ORDER BY elapsed DESC;
text
  pid  | state  | wait_type |  wait_event   |  elapsed     |             query
-------+--------+-----------+---------------+--------------+--------------------------------
 2011  | active | IO        | DataFileRead  | 00:00:42.331 | SELECT * FROM t_order_demo WHERE ...
 2012  | active | LWLock    | BufferContent | 00:00:11.882 | UPDATE t_order_demo SET amount ...
 2013  | active | (null)    | (null)        | 00:00:08.441 | SELECT count(*) FROM t_order_demo

三种情况的处理方向完全不同:

等待类型判定处理方向
IO / DataFileRead真在读磁盘加索引减少扫描、检查缓存命中率、换 SSD
LWLock / BufferContent多个进程抢同一个缓冲页减少热点页争用(如打散热键、降低并发)
(null) 且 active在跑 CPU用 EXPLAIN ANALYZE 看是排序、哈希还是函数计算
sql
-- 结合缓存命中率判断 IO 等待是否是「缓存不够」
SELECT datname,
       blks_hit,
       blks_read,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct,
       round(temp_bytes / 1024.0 / 1024.0, 1) AS temp_mb
FROM pg_stat_database
WHERE datname = current_database();
text
 datname  |  blks_hit  | blks_read | hit_pct | temp_mb
----------+------------+-----------+---------+---------
 app_prod | 89421184   |    412882 |   99.54 |   12841.2

hit_pct = 99.54% 是健康的;temp_mb 高达 12GB 则说明有大量排序/哈希落盘,需要提高 work_mem 或优化 SQL。

注意事项 ​

  • idle in transaction 必须零容忍:它持有快照与锁,直接阻塞 VACUUM 推进;务必配 idle_in_transaction_session_timeout。
  • fastpath function call 无法被取消:pg_cancel_backend 对它无效,只能 pg_terminate_backend 或等待结束。
  • pg_terminate_backend 会让应用看到「连接被重置」:应用必须具备重连与重试能力,否则会向用户暴露错误。
  • pg_stat_activity 的 query 字段有长度限制:默认由 track_activity_query_size 决定(1KB),长 SQL 会被截断;要完整记录需上调该参数并重启。
  • pg_stat_activity 里的 query 不一定是「正在执行的那条」:state='idle' 时它是最后一条执行的语句,容易误导。
  • 后端进程数不等于 max_connections:还有 walsender、autovacuum worker、并行 worker 等,规划时需额外留量。
  • max_worker_processes 是并行与新后台进程的共享池:设得过小会导致并行查询退化、逻辑复制等无法启动。
  • 长连接的 CacheMemoryContext 不会归还:多租户应用要控制单个会话访问的表数量,或缩短连接生命周期。
  • client backend 的 wait_event = ClientRead 是应用慢:数据库侧无可优化,应排查应用是否在事务中做 RPC/业务计算。
  • track_io_timing = on 才能看到真实 I/O 耗时:否则 pg_stat_activity 与 EXPLAIN 中的 I/O 时间不准确(但开启会有额外开销)。
sql
-- 不推荐:靠调大 max_connections 应对连接泄漏
-- ALTER SYSTEM SET max_connections = 2000;   -- 不推荐,只是把问题推迟并放大内存开销
sql
-- 不推荐:不带自我保护地批量杀会话
-- SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle';
-- 不推荐:会杀掉自己、监控探针与连接池的保活连接

最佳实践 ​

  • 三把超时刀全开:statement_timeout、idle_in_transaction_session_timeout、idle_session_timeout 一起上,形成三层防护。
  • 每个应用使用独立的数据库角色:便于按 usename 区分连接来源,也便于用 ALTER ROLE 做差异化参数。
  • 连接池放在应用与数据库之间:PgBouncer 的 transaction 模式下,后端进程数可压到个位数,是本架构下最有效的优化。
  • 给监控与运维留超级用户通道:连接打满时能连进去执行 pg_terminate_backend 自救。
  • 用 application_name 标注业务模块:JDBC 的 ApplicationName、psycopg 的 application_name 都能设置,排查时能直接定位到服务。
  • 把 pg_stat_activity 的关键查询做成巡检:长事务、锁等待、idle in transaction、连接数水位,四项一起看。
  • 优先 pg_cancel_backend 而非 pg_terminate_backend:前者只中断语句,事务可继续;后者断开连接,代价更高。
  • 开启 log_min_duration_statement 但设合理阈值:记录慢查询是定位问题的前提,阈值过高(如 5s)会漏掉「次慢」但高频的查询。
  • 关闭 fastpath 类不可中断的使用:避免 dblink、大对象操作等阻塞在无法取消的状态。
  • 监控后端进程的内存与 CPU 分布:单进程内存异常增长往往指向连接泄漏或 SQL 未使用游标拉取全量数据。

参考链接 ​