25.2 PostgreSQL 核心运行信号
PostgreSQL 自带两类观察接口:
查询视图很简单,正确解释并不简单。以下事实可以同时成立:
本节目标不是记住所有列,而是掌握一套读法:
完整视图以当前版本官方文档为准: PostgreSQL 18 Monitoring Stats。
25.2.1 会话、事务、等待与锁
先确认统计功能是否开启
关键设置:
它们不是同一个开关:
| 设置 | 作用 | 关闭后的含义 |
|---|---|---|
track_activities |
当前命令与开始时间 | activity 信息受限 |
track_counts |
数据库/表等累计活动 | autovacuum 也依赖它 |
track_functions |
函数调用统计 | 不代表函数没有运行 |
track_io_timing |
数据文件 I/O timing | 时间未测量,不是零成本 |
track_wal_io_timing |
WAL I/O timing | WAL 时间未测量 |
stats_fetch_consistency |
一个事务内统计读取一致性 | 影响缓存/快照行为 |
统计有开销,timing 尤其依赖平台时钟成本;但关闭后必须把“未测量”保留下来。 绝不能把:
解释为 WAL 写入没有花时间。
本章沙箱:
pg_stat_activity 是当前 backend 视图
先使用不导出 query text 的聚合:
为什么带 backend_type?PostgreSQL 18 里不只有 client backend:
如果把后台进程与 client backend 混在一起,某个长期运行的 background worker 可能被误判为“用户 SQL 运行几小时”。
state 与 wait_event 独立
常见错误:
实际:
所以等待查询写成:
这条查询没有读取 query 列。需要 SQL 上下文时,应在受限交互会话中按
queryid、application、database 和 owner 缩小范围,避免把全文复制进工单。
wait event 是“正在等什么”,不是“根因”
wait_event_type 先把等待分大类:
同一种等待可能有多种机制:
因此:
才形成诊断。
PostgreSQL 18 的异步 I/O 引入 io worker 等 backend type 和相应等待。升级后
不要假设旧版 wait event 列表仍完整;dashboard 和规则要按当前版本校验。
当前统计在一个事务里可能保持不变
累计统计不是每次访问都无条件读取最新值。PostgreSQL 会把统计写入共享内存, 各进程最迟按一定节奏 flush;访问者又可能在当前事务内缓存读取结果。
这段会造成困惑:
在 stats_fetch_consistency=cache 下,同一事务后续读取可能继续看到缓存值。
诊断时优先:
pg_stat_clear_snapshot() 清的是当前 session 的统计 snapshot,不是重置全局
统计;不要与 pg_stat_reset* 混淆。
统计更新也有时间边界
累计统计通常在 transaction 完成后才反映:
因此调查进行中的大事务:
- activity 看当前 transaction age;
- locks 看当前持有/等待;
- progress 看支持的维护动作;
- WAL/IO counter 看累计变化;
- 不等待累计表统计“先证明它存在”。
crash、恢复与复制会改变统计历史
PostgreSQL 正常关闭会保存累计统计;非正常关闭、从 base backup 恢复或 PITR 可能导致统计 reset。跨 failover 比较时:
必须同时保存:
- member/timeline;
- stats reset;
- postmaster start;
- role transition;
- source instance;
- sampling window。
long query 与 long transaction 不同
场景:
| 状态 | query age | xact age | 风险 |
|---|---|---|---|
| active long query | 长 | 约等于或短于 xact | 执行/等待资源 |
| idle in transaction | 当前 query 已结束 | 长 | lock、xmin、vacuum |
| active in old transaction | 当前 query 短 | 很长 | snapshot/业务批次 |
| idle | 上条 query 的开始时间不代表在执行 | 无 transaction | 通常只是连接 |
因此不要用 query_start 对所有 state 排序然后自动 cancel。
idle in transaction 为什么危险
它可能:
- 保留 row/table lock;
- 持有旧 snapshot;
- 阻碍 dead tuple 回收;
- 拉长
backend_xmin; - 占用 connection/pool slot;
- 让后续应用错误更难定位。
诊断字段:
取消或终止是变更动作,不属于本章 L0 采集。先确认:
- owner/application;
- transaction 是否仍有不可重试副作用;
- pool mode;
- unknown commit outcome;
- cancel 与 terminate 的差异;
- rollback/重连影响;
- 用户症状是否关联。
pg_locks 是锁申请,不是完整业务解释
安全聚合:
找 blocker 可使用 pg_blocking_pids():
这个函数给出 blocker PID,但仍要判断:
锁图而不是最长列表
事故中更有用的是:
并保存:
- edge 采样时刻;
- blocker state;
backend_xid/xmin;- queryid,而不是默认 query text;
- application/release;
- lock type/mode;
- user impact。
一条锁边可能瞬间消失。诊断包应保存有界快照,而不是事后只看当前视图。
deadlock 与普通阻塞
普通 lock wait 可以持续;deadlock 是一个等待环,PostgreSQL 会检测并中止其中 一个 transaction。
观察:
log_lock_waits=on 只在等待超过 deadlock_timeout 后记录,短等待不会出现。
日志“没有 lock wait”不能证明没有短暂锁竞争。
权限边界
普通用户只能看到其他 session 的有限信息。pg_read_all_stats 能读取全库统计和
其他 session 的更多信息,但这仍然是高敏感可观测权限:
- query text 可能含业务值;
- application name 可能带身份;
- client address 暴露拓扑;
- activity 能推断业务行为。
建议:
不要因为它不是 superuser 就把它当低风险权限。
查询本身也会进入观察结果
读取 pg_stat_activity 时,你自己的查询也是 active;访问许多系统视图也会拿
AccessShareLock。本章正式快照出现的 relation locks 就包括采集查询本身。
因此:
- 标识 collector application;
- 从结果中区分自身;
- 限制 statement timeout;
- 不在 tight loop 高频轮询;
- 避免一次展开所有 query text;
- 将 observer effect 写进证据。
25.2.2 缓冲、I/O、WAL、检查点与复制
数据路径不是“内存或磁盘”二选一
一个 PostgreSQL page 读取可能经过:
所以:
pg_stat_io 明确不区分物理磁盘和 OS page cache。必须结合 node/block-device
证据。
pg_stat_database 给出数据库级累计轮廓
常用字段:
它适合:
- database-level rate;
- hit/read 变化;
- temp spill 趋势;
- rollback/deadlock 变化;
- reset-aware baseline。
不适合:
- 归因到具体 query;
- 直接推物理 IOPS;
- 用
blks_hit / (blks_hit + blks_read)单独判断内存是否足够; - 比较不同 reset 区间的裸总数。
cache hit ratio 不是性能分数
$$ \text{hit ratio}
\frac{\Delta hits} {\Delta hits + \Delta reads} $$
即使正确用窗口增量,也受 workload 影响:
- 大表顺序扫描天然产生 reads;
- 小表热点容易高命中;
- OS cache 命中仍记为 PostgreSQL read;
- 低 hit 可能是合理批处理;
- 高 hit 不代表 CPU、lock 或 plan 健康。
把它作为 workload 特征,不要设一个跨服务的“低于 99% 就 page”。
pg_stat_io 按谁、什么对象、什么上下文拆分
PostgreSQL 18 可按:
观察:
先聚合非零工作:
窗口诊断需要两次快照求 delta 或 exporter counter rate。裸累计值只说明 reset 以来总量。
timing 要与 byte/count 一起读
平均值会掩盖 tail。需要时结合:
- block-device latency histogram;
- request latency histogram;
- trace sampled tail;
- queryid-level I/O;
- workload mix。
buffer、backend 与 checkpoint 写入
dirty buffer 可能由 backend、background writer、checkpointer 等路径写出。
pg_stat_io 帮助按 backend type 区分;pg_stat_checkpointer 给 checkpoint
累计结果。
PostgreSQL 18:
重要字段包括:
解释:
不要只看一次 write_time 总值。计算每窗口的 checkpoint count、buffer、write
和 sync delta,再与用户 latency、WAL rate 和 host I/O 对齐。
checkpoint 不是越少越好
过频可能增加写入压力和 full-page image;过稀可能:
- 增加 crash recovery 时间;
- 需要更多 WAL;
- 在 checkpoint 集中更多工作;
- 改变恢复和容量特征。
任何调参要结合:
本章只观察,不修改 checkpoint_timeout、max_wal_size 或 completion target。
pg_stat_wal 是 WAL 生成轮廓
用途:
- WAL byte rate;
- full-page image 比例变化;
- WAL buffer full;
- 与 write workload、checkpoint、replication、archive 对齐。
不能直接说明:
- 哪个 query 产生 WAL;
- WAL 是否已归档;
- replica 是否已回放;
- recovery 能否成功。
query 归因需要 pg_stat_statements.wal_bytes 等;归档与恢复需要另外的证据。
full-page image 的上下文
checkpoint 后 page 首次修改可能记录 full-page image,以支持恢复。wal_fpi
变化与:
- checkpoint 频率;
- 工作集;
full_page_writes;- page 修改模式;
- compression;
- backup/recovery policy
相关。不要看到 FPI 高就关闭安全机制。
pg_stat_archiver 同时有 counter 和最后事件
三种不同问题:
沙箱快照:
最近成功晚于失败,说明历史 counter 非零不能证明当前仍失败。
候选告警:
这仍只是 recovery risk candidate。下一步要复核:
- 当前 WAL 是否继续生成;
- archive command/pgBackRest 状态;
- repository;
- latest archive;
- backup/WAL coverage;
- 最近 restore drill。
“归档恢复”不等于“恢复就绪”。
replication 有位置、时间、状态三套语义
primary:
replica:
公开/长期证据不一定应保存 sender/client address;可以保留 member identity、 state、sync_state 和位置差。
位置 gap
位置差按 WAL byte 计,不是秒:
因为 replay throughput、workload、conflict、I/O 和 future WAL 都会变化。它可 用于容量与趋势,不应直接承诺 RTO。
时间 lag 可能是 NULL
write_lag、flush_lag、replay_lag 是最近同步交互产生的测量,并不保证
持续给出“当前落后秒数”。空闲系统可能为 NULL;这不等于零,也不等于故障。
使用:
- LSN distance;
- connection/state;
- last message;
- known commit probe;
- workload/WAL generation
共同解释。
本章正式 SQL 快照中,两条 streaming async replica 的 LSN gap 都为 0,而
lag interval 为 NULL。这正好说明:
sync_state 与 commit durability
sync_state=async 表示它不是当前同步确认的一部分。即使 gap 为零:
- 后续 commit 仍可能尚未复制;
- primary 立即丢失会有 write gap 风险;
- 应用 commit acknowledgment 语义取决于
synchronous_commit和配置; - user freshness 取决于读取路径。
把 pg_stat_replication 与第 20 章 HA 合同一起读,不要从瞬时 gap 倒推出
durability guarantee。
replication slot 与 WAL retained risk
除了 replica gap,还要观察:
slot identity 可能属于 extension/consumer;不要自动删除 inactive slot。风险:
- WAL retention 填满磁盘;
- logical consumer 落后;
- slot 失效;
- consumer 被误认作废弃。
容量规则默认 ticket,动作需 owner 和 consumer 证据。
复制冲突
hot standby 查询可能与 replay 冲突。观察:
pg_stat_database_conflicts;- replica activity;
- query cancellation log;
- feedback/delay 设置;
- retained xmin/WAL;
- user read path。
降低冲突的设置可能增加 bloat 或 freshness lag,不能只优化一张图。
25.2.3 vacuum、冻结、膨胀与对象增长
vacuum 有四个主要目的
常规 vacuum 不是“清空表”,而是:
- 回收 dead row version,使空间可在表内复用;
- 更新 visibility map,支持 index-only scan 等;
- 防止 transaction ID wraparound;
- 维护统计/冻结等运行状态。
ANALYZE 更新 planner statistics;它与 vacuum 可以一起运行,但目的不同。
autovacuum 依赖统计
track_counts=on 不只是“多收指标”。autovacuum 使用累计活动决定何时处理表。
关闭它会影响维护机制。
每表触发近似由:
决定,并受 insert threshold、per-table storage parameter、cost limit、worker 数、 全局设置等影响。
不能只看“autovacuum process 存在”。要看:
- table change pressure;
- last vacuum/autovacuum;
- dead tuples;
- analyze freshness;
- blockers;
- progress;
- freeze age;
- runtime/duration。
表级统计是 estimate + counter
n_live_tup、n_dead_tup 是估算;count 是自 reset 累计。它们适合找候选,不
适合直接计算“精确 bloat 百分比”。
dead tuple 不等于 bloat
vacuum 后 n_dead_tup 可能下降,但文件通常不缩小,因为普通 vacuum 将空间
留给表内复用。需要归还操作系统的操作通常更重,涉及 rewrite/lock/extra disk;
不能因为文件没缩就说 vacuum 失败。
长事务如何阻止回收
MVCC 需要保留旧版本给仍可能看见它的 snapshot。长 transaction、prepared transaction、replication slot/feedback 等可能拉住 xmin:
观察:
还要查:
- prepared transactions;
- replication slots;
- replica feedback;
- vacuum progress;
- table-level age。
“找到最老 PID 就 terminate”不是安全策略。
freeze age 是剩余空间,不是普通 latency
transaction ID 是有限循环空间。旧 tuple 必须被 freeze,避免 wraparound 后 可见性灾难。
数据库级:
表级:
不要把阈值写成与配置无关的魔法数字。应比较:
本章候选 PG36FreezeAgeHorizon 使用固定数字只是 isolated lab 的测试输入,
明确标为 proposed;生产规则应由当前配置和容量政策生成。
anti-wraparound autovacuum 的特殊性
为防 wraparound 启动的 autovacuum 通常不会像普通 autovacuum 那样轻易被冲突 动作自动打断。不要把它当“可以随时 kill 的后台噪声”。
如果已经进入紧急区:
- 停止增加风险的长事务;
- 找出不能推进的表与 blocker;
- 评估 I/O/空间/锁;
- 按 runbook 控制维护;
- 不并行执行未经评估的 rewrite;
- 保留 evidence。
最优策略是在容量 horizon 阶段用 ticket 解决,而不是等 emergency page。
progress view 是当前进度,不是历史
vacuum:
PostgreSQL 还为:
ANALYZE;CREATE INDEX/REINDEX;CLUSTER/VACUUM FULL;COPY;- base backup
提供相应 progress view,具体列按版本文档。
没有行只表示当前没有该动作被报告,不表示:
- 从未运行;
- 上次成功;
- 下一次会成功;
- 没有被瞬间启动后失败。
历史需要日志、事件和累计 count。
progress 百分比可能不单调
不同 phase 使用不同总量;并行、索引清理和 dead item cycle 也会改变解释。 不要把:
当成整个 vacuum 的精确完成百分比。应同时显示 phase 和相关量。
analyze freshness
planner statistics 变旧会导致估算偏差。候选信号:
n_mod_since_analyze 是估算/累计线索,不是每行精确 change counter。对热点、
分区和高度偏斜列,需要 workload-aware 策略。
对象增长要拆分
增长可能来自:
- 正常业务数据;
- index 数量;
- TOAST;
- dead/reusable space;
- fillfactor;
- 分区保留;
- 临时/中间对象;
- rewrite;
- 失控 batch。
不要只按总大小 page。需要:
容量是预测和计划问题,默认 ticket。
partition 会改变聚合方式
父表和各分区的:
- size;
- table stats;
- autovacuum;
- analyze;
- index;
- freeze age
需要分别观察,再按业务分区策略聚合。只看父表可能近乎空;只列每个分区又会 产生巨大 cardinality。
推荐:
不要把每个临时分区名永久做成高基数 alert label。
bloat estimate 的边界
extension 或 SQL 估算 bloat 常依赖:
- row width;
- null bitmap;
- alignment;
- fillfactor;
- statistics;
- page sample;
- index type。
它是排序候选,不是字节级财务账。要做重操作前:
- 复核对象大小与增长趋势;
- 判断空间能否复用;
- 找出生成机制和 blocker;
- 评估 rewrite/lock/replication/WAL/backup 影响;
- 准备额外磁盘和回退/前滚;
- 在维护窗口验证。
本章不会自动 VACUUM FULL 或 REINDEX。
把三类维护信号分开
| 类别 | 问题 | 典型动作 |
|---|---|---|
| 运行正确性 | wraparound 是否接近 | 高优先级维护/停止风险来源 |
| 性能卫生 | dead tuple、stats 是否影响 workload | vacuum/analyze 调整与 blocker 修复 |
| 容量 | 对象/索引/WAL 是否耗尽空间 | retention、扩容、结构优化、rewrite 计划 |
它们的 severity、owner 和时间尺度不同。一个“表大”告警不能同时代表三者。
沙箱快照如何读
正式采集在 pg-test-1 观察到:
这不是“vacuum 永远健康”的证明:
- synthetic workload 很小;
- 只有一次瞬时快照;
- 没有长期增长率;
- 没有生产 transaction rate;
- 没有生产配置与 margin;
- estimate 可能变化。
可以得出的结论只有:
PostgreSQL 信号最小关联表
| 用户症状 | PG 入口 | 原生证据 | 外部复核 |
|---|---|---|---|
| latency | active/wait | activity、locks、queryid | pool、host、trace |
| errors | rollback/deadlock | database counter、logs | app outcome |
| stale read | replica path | replication position/state | commit token probe |
| write latency | WAL/checkpoint | wal、checkpointer、I/O | device、app histogram |
| query spill | temp bytes | database、statement、logs | plan/work_mem policy |
| vacuum delay | old xmin | activity、table stats、progress | workload/change event |
| freeze risk | XID age | database/class age | rate/horizon/capacity |
| recovery risk | archive | archiver + WAL | pgBackRest + restore drill |
本节验收
你应当能解释:
active为什么仍可能在等待;- 同一 transaction 为什么可能读到缓存的累计统计;
track_wal_io_timing=off为什么不能把时间解释为零;pg_stat_io为什么不等于物理磁盘 I/O;failed_count>0为什么不等于当前归档失败;- time lag
NULL与 WAL gap0为什么可以同时出现; - WAL gap
0为什么不能证明用户新鲜度; n_dead_tup为什么不等于精确 bloat;- 普通 vacuum 为什么通常不缩小文件;
- progress view 没有行为什么不证明历史成功;
- query、lock、vacuum 和 freeze 信号分别对应哪种动作;
- 任何取消、终止、reset、vacuum 或配置变更为什么不属于 L0 观察。
上一节:从问题选择可观测信号 · 返回本章目录 · 下一节:SQL 可观测基线 · 查看全书目录 · 查看索引中心