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 Protocol | Parse/Bind/Describe/Execute 分步,支持参数与游标 |
| Portal | Portal | 扩展协议下执行计划的运行实例,游标(CURSOR)的底层 |
| 并行 worker | Parallel Worker | 为单个查询派生的辅助进程,backend_type = 'parallel worker' |
| 等待事件 | Wait Event | 后端进程此刻在等什么(锁、IO、WAL、客户端……) |
| 本地内存 | Local Memory | 后端私有的 work_mem、catalog cache、temp_buffers 等 |
| 事务状态 | Transaction State | idle / 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。
后端进程的三类内存
后端进程的内存由三部分组成,理解它们就能理解连接数为什么不能太多:
┌───────────────────────────────────────────────────────────┐
│ ① 共享内存(所有进程共享,后端只读/受控写) │
│ shared_buffers / wal_buffers / 锁表 / ProcArray │
│ → 不随连接数线性增长 │
├───────────────────────────────────────────────────────────┤
│ ② 后端固定开销(每个后端进程独有,约 5~10MB 起) │
│ 进程栈、执行器状态、relcache/syscache 初始条目 │
│ → 随连接数线性增长,是连接数的「地板成本」 │
├───────────────────────────────────────────────────────────┤
│ ③ 后端动态内存(按操作分配,用完释放) │
│ work_mem(每个排序/哈希节点)/ temp_buffers / │
│ catalog cache(随访问过的目录对象数量增长,通常不归还) │
│ → 峰值随并发查询数与查询复杂度放大 │
└───────────────────────────────────────────────────────────┘-- 查看当前后端进程的内存上下文分布(只对当前会话有效)
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; 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。
核心机制
后端进程的完整生命周期
① 连接建立
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 → 回收-- 完整观察一个会话的生命周期关键时间点
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(); 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 | 持有锁与快照,阻塞 VACUUM | idle_in_transaction_session_timeout 强制回收 |
idle in transaction (aborted) | 事务中出过错但未回滚 | 同上有害,且客户端逻辑有 bug | 必须回滚或断开 |
fastpath function call | 执行不可中断的函数(如 dblink) | 无法用 pg_cancel_backend 中断 | 谨慎使用此类函数 |
-- 按状态汇总连接,快速发现异常状态
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; 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)-- 直接揪出危险的 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; 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 ...超时参数的正确配置顺序
这三个超时参数分别防三种问题,建议全部显式设置:
# 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 | 含义与处理 |
|---|---|---|
LWLock | BufferContent、WALWrite | 轻量锁争用,通常是热点页或 WAL 写瓶颈 |
Lock | transactionid、relation、tuple | 等事务/表/行锁,用 pg_blocking_pids() 找阻塞者 |
IO | DataFileRead、DataFileWrite | 真实磁盘 I/O,检查是否缓存命中率不足 |
Client | ClientRead、ClientWrite | 等客户端收发数据,通常是应用侧慢 |
IPC | ParallelWaitForWorkers、SyncRep | 等并行 worker 或同步复制确认 |
BufferPin | BufferPin | 等缓冲区的 pin 释放 |
Timeout | BaseBackup、CheckpointWriteDelay | 主动的等待(节流、重试) |
Extension | extension | 扩展内部等待 |
Activity | AutovacuumMain、CheckpointerMain | 辅助进程的空闲等待(正常) |
-- 按等待事件归类当前活跃会话:一眼看出瓶颈在哪一层
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; 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 密集(大排序、复杂函数计算)。
-- 定位锁阻塞链:谁在等谁
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; 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)-- 顺藤摸瓜查看阻塞者本身的信息
SELECT pid, state, xact_start, now() - xact_start AS xact_age, left(query, 60)
FROM pg_stat_activity
WHERE pid = 1402; 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 等驱动默认 |
-- psql 中观察客户端编码与协议相关信息
SELECT name, setting
FROM pg_settings
WHERE name IN ('client_encoding', 'standard_conforming_strings', 'extra_float_digits')
ORDER BY name; name | setting
----------------------------+---------
client_encoding | UTF8
extra_float_digits | 1
standard_conforming_strings | on# ── 用 psql 观察扩展协议下预处理语句的复用 ──
psql -h localhost -U app_user -d app_prod -c "\prepared" 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。
-- 查看并行相关配置
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; 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)-- 观察并行查询:Gather 节点下的 workers planned / launched
EXPLAIN (ANALYZE, TIMING off)
SELECT count(*) FROM t_order_demo WHERE amount > 5000; 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)-- 并行 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; 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 接口 | 走崩溃恢复风险 | — |
-- 优先使用 pg_cancel_backend:温和,只打断当前语句
SELECT pg_cancel_backend(18823); pg_cancel_backend
-------------------
t-- 谨慎使用 pg_terminate_backend:会断开连接,应用需能重连
SELECT pg_terminate_backend(1402),
(SELECT application_name FROM pg_stat_activity WHERE pid = 1402) AS app; pg_terminate_backend | app
----------------------+----------------
t | app-server-01-- 批量终止超时的 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(); killed | pid | application_name
--------+------+------------------
t | 1402 | app-server-01
(1 row)权限与自我保护
pg_terminate_backend需要超级用户或pg_signal_backend角色,且只能终止同角色的会话。- 批量终止前必须排除
pg_backend_pid()自身,否则可能把自己干掉。 fastpath function call状态的后端无法被取消(它在执行不可中断的函数),只能等它结束或直接 terminate。
-- 授权运维账号具备终止其他会话的能力(而不给超级用户)
GRANT pg_signal_backend TO ops_user;会话级参数与 ALTER ROLE 的覆盖机制
后端进程初始化时会按「数据库 → 角色 → 会话」的优先级应用参数覆盖,这也是为什么同一个参数在不同会话里值可能不同。
优先级从低到高:
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() (会话级)-- 为高频报表用户单独提高 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';-- 用 pg_settings.source 验证参数的最终来源
SELECT name,
setting,
source,
sourceline -- 若来自配置文件,显示行号;来自 ALTER 则为空
FROM pg_settings
WHERE name IN ('work_mem', 'maintenance_work_mem', 'statement_timeout'); 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:完整追踪一个会话的内存与状态
-- ── 第 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; my_pid | instance_start | db | usr | work_mem
--------+-----------------------------+----------+----------+----------
1901 | 2026-09-20 09:12:33.418+08 | app_prod | app_user | 4MB
(1 row)-- ── 第 2 步:执行一个需要大量本地内存的操作,观察内存上下文变化 ──
EXPLAIN (ANALYZE, TIMING off)
SELECT user_id, count(*), sum(amount)
FROM t_order_demo
GROUP BY user_id
HAVING sum(amount) > 100000; 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 小于所需,会变成外部排序,性能下降一个数量级。
-- ── 第 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; 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:复现并解决连接泄漏
-- ── 第 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'; total_conns | idle_conns | active_conns | max_conns
-------------+------------+--------------+-----------
196 | 178 | 18 | 200196/200,非常危急。178 个空闲连接是典型泄漏特征。
-- ── 第 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; 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 点就在——这不是忙,是泄漏(连接池配置错误或未归还连接)。
# postgresql.conf —— 兜底防护,即使应用没修好也不会拖垮实例
idle_session_timeout = '10min' # 空闲连接 10 分钟自动断开
idle_in_transaction_session_timeout = '60s' # 事务中空闲 1 分钟强制回滚-- ── 第 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(); killed | application_name | idle_for
--------+------------------+---------------
t | app-server-03 | 02:41:07.882
t | app-server-03 | 02:38:50.221
...示例 3:用等待事件定位「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; 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 看是排序、哈希还是函数计算 |
-- 结合缓存命中率判断 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(); datname | blks_hit | blks_read | hit_pct | temp_mb
----------+------------+-----------+---------+---------
app_prod | 89421184 | 412882 | 99.54 | 12841.2hit_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 时间不准确(但开启会有额外开销)。
-- 不推荐:靠调大 max_connections 应对连接泄漏
-- ALTER SYSTEM SET max_connections = 2000; -- 不推荐,只是把问题推迟并放大内存开销-- 不推荐:不带自我保护地批量杀会话
-- 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 未使用游标拉取全量数据。
下一节