Skip to content

pg_stat_database 与 pg_stat_user_tables ​

如果说前面几页的视图是在「放大镜」下看会话、锁、SQL、索引,那么 pg_stat_database 就是整库的「仪表盘」:一行对应一个数据库(外加集簇共享对象),汇总了事务提交/回滚数、块命中/读取数、元组增删改数、临时文件量、死锁次数、会话统计等最核心的健康指标。缓存命中率、死锁趋势、临时文件溢出、连接质量——运维巡检看板的绝大多数数字,源头都在这里。

围绕它还有一群「家族视图」:备库上的恢复冲突计数在 pg_stat_database_conflicts;表级的块 I/O 在 pg_statio_user_tables;PG16 引入的 I/O 全景视图 pg_stat_io;以及后台写进程的 pg_stat_bgwriter(较新版本拆分出了 checkpointer 与 SLRU 统计)。本页把它们作为一个整体来讲解。

pg_stat_user_tables(表的顺序扫描、变更、清理统计)已在上一页与索引统计一起讲过,本页只在与库级指标联动时回顾它的角色。所有这些统计都是自实例启动或统计重置以来的累计值,解读时务必带上统计周期。

前置知识

建议先阅读:pg_stat_user_indexes 索引使用统计

基础概念 ​

统计收集器为每个数据库维护一份计数汇总。pg_stat_database 的粒度是「数据库」:它不区分哪条 SQL、哪个索引,只回答「这个库整体上发生了多少」。正因为它足够粗,才能长期高频采样而不产生性能负担,是监控系统的理想数据源。

视图粒度核心用途
pg_stat_database每个数据库一行命中率、事务量、临时文件、死锁、会话质量
pg_stat_database_conflicts每个数据库一行备库恢复冲突的分类计数
pg_statio_user_tables每张用户表堆/索引/TOAST 块的命中与读盘分布
pg_stat_io(PG16+)backend_type × 对象 × 场景I/O 行为全景(读写、命中、驱逐、fsync)
pg_stat_bgwriter集簇后台写与检查点行为(较新版本拆分为多个视图)

采样思路:库级指标适合「每分钟采一次、按天聚合出趋势」;表级与 I/O 级视图适合「巡检下钻」。把两者组合起来,就是「整体异常 → 定位到表 → 定位到 I/O 场景」的标准排障路径。

语法与用法 ​

sql
-- 1. 全库健康总览
SELECT datname,
       numbackends,
       xact_commit,
       xact_rollback,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_ratio,
       pg_size_pretty(temp_bytes) AS temp_written,
       deadlocks,
       sessions
FROM pg_stat_database
WHERE datname IS NOT NULL
ORDER BY xact_commit DESC;
text
   datname   | numbackends | xact_commit | xact_rollback | hit_ratio | temp_written | deadlocks | sessions
-------------+-------------+-------------+---------------+-----------+--------------+-----------+----------
 appdb       |          57 |    84201391 |        214092 |     99.42 | 21 GB        |        37 |   184021
 reportdb    |           3 |     1204899 |           631 |     91.07 | 480 GB       |         0 |     9213
 postgres    |           1 |        4102 |            12 |     99.98 | 0 bytes      |         0 |     4110
(3 rows)
sql
-- 2. 增量视角:两次采样相减得到「这段时间发生了多少」
-- 第一次采样
CREATE TABLE pg_stat_database_snap AS
SELECT now() AS taken_at, * FROM pg_stat_database;

-- 一段时间后计算增量
SELECT b.datname,
       b.xact_commit - a.xact_commit AS commits_delta,
       b.blks_read - a.blks_read AS blks_read_delta,
       b.temp_bytes - a.temp_bytes AS temp_bytes_delta
FROM pg_stat_database_snap a
JOIN pg_stat_database b USING (datid)
WHERE b.datname IS NOT NULL;
sql
-- 3. 表级 I/O 下钻
SELECT relname,
       heap_blks_read, heap_blks_hit,
       idx_blks_read, idx_blks_hit,
       toast_blks_read, toast_blks_hit
FROM pg_statio_user_tables
ORDER BY heap_blks_read DESC
LIMIT 10;

详细说明 ​

字段全览(一):活动与事务 ​

字段类型说明
datid / datnameoid / name数据库标识;较新版本还有一行 datid = 0 的记录代表集簇共享对象
numbackendsinteger该库当前连接的后端数(实时值,非累计)
xact_commitbigint累计已提交事务数
xact_rollbackbigint累计回滚事务数

xact_rollback / (xact_commit + xact_rollback) 是「事务失败率」。长期偏高的库通常意味着应用把错误当流程在用(依赖异常做业务判断),或连接池健康检查过于激进。

字段全览(二):缓冲区与命中率 ​

字段类型说明
blks_readbigint累计从存储读入 shared_buffers 的块数
blks_hitbigint累计在 shared_buffers 中命中的块数

缓存命中率公式:

sql
SELECT datname,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_ratio
FROM pg_stat_database
WHERE blks_hit + blks_read > 0;

解读要点:

  • OLTP 库的健康线通常在 99% 上下;命中率每下降 0.1% 都对应可观的磁盘读增量。
  • blks_read 只代表「穿透 shared_buffers 的读」,后续还有操作系统页缓存兜底,所以命中率低不必然等于磁盘瓶颈——但它是第一信号。
  • 该指标是全库累计的,冷热数据混合会稀释异常;下钻用 pg_statio_user_tables 定位到表。

字段全览(三):元组统计 ​

字段类型说明
tup_returnedbigint扫描层「返回」的行数(顺序扫描读过的行 + 索引扫描读过的条目)
tup_fetchedbigint实际取回给上层处理的存活行数
tup_inserted / tup_updated / tup_deletedbigint累计插入/更新/删除的行数

tup_returned 与 tup_fetched 的差距是「扫描效率」的库级画像:returned 远大于 fetched,说明大量行被读进来又被过滤掉——全表扫描或低选择性索引的典型特征。元组变更速率(tup_inserted/updated/deleted 的增量)则是容量规划与 autovacuum 压力评估的基础数据。

字段全览(四):临时文件与死锁 ​

字段类型说明
temp_filesbigint累计创建的临时文件个数
temp_bytesbigint累计写入临时文件的字节数
deadlocksbigint累计死锁次数

temp_bytes 是排查排序/哈希溢出的第一指标:持续增长的库要么 work_mem 偏小,要么存在失控的大查询(结合 pg_stat_statements 的 temp_blks_written 榜单定位到语句)。deadlocks 非 0 就值得回查日志里的 deadlock detected 明细,属于《死锁检测机制》一页的范畴。

字段全览(五):会话统计(PG14+) ​

PG14 起新增了一组会话质量指标,是连接治理的量化依据:

字段类型说明
session_timedouble precision所有会话的累计存活时长(毫秒)
active_timedouble precision会话处于 active 状态的累计时长
idle_in_transaction_timedouble precision处于 idle in transaction 的累计时长
sessionsbigint累计会话数
sessions_abandonedbigint客户端异常断开(未发送 Terminate)的会话数
sessions_fatalbigint因致命错误结束的会话数
sessions_killedbigint被管理员/超时机制终止的会话数
sql
-- 会话质量画像
SELECT datname,
       sessions,
       round(100.0 * sessions_abandoned / nullif(sessions, 0), 2) AS abandoned_pct,
       round(100.0 * active_time / nullif(session_time, 0), 2)   AS active_pct,
       round((idle_in_transaction_time / 1000 / 3600)::numeric, 1) AS idle_in_tx_hours
FROM pg_stat_database
WHERE sessions > 0;

sessions_abandoned 持续增长说明客户端频繁异常断连(超时被杀、进程崩溃、网络抖动);idle_in_transaction_time 占比高直接指向长事务治理。

字段全览(六):校验和与恢复冲突 ​

sql
-- 数据页校验和失败(PG12+,需 initdb 时开启 checksums)
SELECT datname, datchecksum_failures, datchecksum_last_failure
FROM pg_stat_database
WHERE datchecksum_failures > 0;

datchecksum_failures 与 datchecksum_last_failure 记录页校验和失败——非零值是存储层损坏的强烈信号,应立即触发告警与排查。

备库场景下,pg_stat_database_conflicts 分类记录恢复冲突:

sql
SELECT datname,
       confl_tablespace,
       confl_lock,
       confl_snapshot,    -- 快照过老被取消(经典:备库长查询 vs 回放)
       confl_bufferpin,
       confl_deadlock
FROM pg_stat_database_conflicts;
字段冲突场景
confl_tablespace备库查询还依赖的表空间在主库被删除
confl_lock备库查询持有的锁与回放所需的锁冲突
confl_snapshot查询快照比 WAL 回放进度老(recovery_min_apply_delay 或长查询)
confl_bufferpin查询钉住的缓冲区阻碍回放
confl_deadlock恢复过程中的死锁

confl_snapshot 是备库长查询问题的常见来源,治理手段包括 hot_standby_feedback、max_standby_streaming_delay 的取舍——详见《pg_stat_replication 监控》一页。

stats_reset 与统计口径 ​

sql
SELECT datname, stats_reset FROM pg_stat_database;

每个数据库的统计都可以被 pg_stat_reset() 单独重置(这点与表级统计一致)。stats_reset 列记录重置时间——所有基于 pg_stat_database 的报表都必须显示它,否则「累计数」无法换算成「速率」。

pg_statio_user_tables:表级块 I/O ​

字段组字段说明
堆heap_blks_read / heap_blks_hit表数据块的读盘/命中
索引idx_blks_read / idx_blks_hit该表全部索引块的读盘/命中
TOASTtoast_blks_read / toast_blks_hit大字段压缩存储区的读盘/命中
TOAST 索引tidx_blks_read / tidx_blks_hitTOAST 索引的读盘/命中
sql
-- 找出读盘最重的表及其 I/O 构成
SELECT relname,
       heap_blks_read,
       idx_blks_read,
       toast_blks_read,
       round(100.0 * (heap_blks_hit + idx_blks_hit + toast_blks_hit + tidx_blks_hit) /
             nullif(heap_blks_hit + heap_blks_read +
                    idx_blks_hit + idx_blks_read +
                    toast_blks_hit + toast_blks_read +
                    tidx_blks_hit + tidx_blks_read, 0), 2) AS overall_hit_ratio
FROM pg_statio_user_tables
ORDER BY heap_blks_read + idx_blks_read + toast_blks_read DESC
LIMIT 10;

toast_blks_read 异常大的表通常存了大文本/JSONB 且高频读取——考虑收窄查询列或压缩策略,而不是盲目加内存。

pg_stat_io:I/O 全景(PG16+) ​

PG16 引入的 pg_stat_io 把 I/O 统计从「表/库粒度」升级为「进程类型 × 对象类型 × 操作场景」的三维矩阵:

字段说明
backend_type进程类型:client backend、autovacuum worker、checkpointer 等
object对象类型:relation(数据)、temp(临时)等
context操作场景:normal、vacuum、vacuum_full、bulkread、bulkwrite、logicalrep 等
reads / read_time读操作次数与耗时(需 track_io_timing = on 才有时间值)
writes / write_time写操作次数与耗时
extends / extend_time追加新块的次数与耗时
hits缓冲区命中次数
evictions因需要复用而驱逐脏/干净块的次数
reuses批量场景(如 vacuum)复用缓冲环的次数
fsyncs / fsync_timefsync 次数与耗时
op_bytes该行每次操作的字节数(通常为块大小)
stats_reset统计重置时间
sql
-- I/O 总览:谁在读盘、什么场景在读
SELECT backend_type,
       object,
       context,
       reads,
       pg_size_pretty((reads * op_bytes)::bigint) AS read_volume,
       round(read_time::numeric, 1) AS read_ms,
       evictions,
       fsyncs
FROM pg_stat_io
ORDER BY reads DESC
LIMIT 15;

经典用法是区分「业务读盘」(backend_type = client backend)与「维护读盘」(autovacuum worker + context = vacuum):前者指向缓存不足或索引缺失,后者是正常的清理行为,混在一起看会误判。

后台写进程统计:pg_stat_bgwriter 及其拆分 ​

pg_stat_bgwriter 长期承担两份职责:检查点统计与后台写统计。较新版本(PG17 起)把它拆分成了更清晰的几个视图:

视图主要字段观察
pg_stat_checkpointer(PG17+)checkpoints_timed / checkpoints_req / checkpoint_write_time / checkpoint_sync_time / buffers_written检查点节奏是否健康:req 占比高说明 max_wal_size 偏小
pg_stat_bgwriter拆分后保留 buffers_clean / maxwritten_clean / buffers_alloc 等后台写是否跟得上:maxwritten_clean 高说明 bgwriter 能力不足
pg_stat_slru(PG13+)各 SLRU 缓存的 blks_hit / blks_read / blks_written / blks_exists / flushes / truncatesSLRU 缓存压力(PG17 起部分缓存大小可通过专用 GUC 调整)

PG16 及更早版本直接查 pg_stat_bgwriter 拿全部字段即可,语义相同。

numbackends 与连接容量规划 ​

numbackends 是 pg_stat_database 里唯一的「实时」列,其余全是累计值。它是连接容量规划的主数据源:

sql
-- 各库连接分布与总量的瞬时读数
SELECT datname,
       numbackends,
       round(100.0 * numbackends /
             current_setting('max_connections')::int, 1) AS pct_of_max
FROM pg_stat_database
WHERE datname IS NOT NULL
ORDER BY numbackends DESC;

容量规划的三个口径:

  1. 业务连接:numbackends 的合计扣掉系统库(postgres)与模板库的行,再对照 pg_stat_activity 里 backend_type = 'client backend' 的计数交叉验证。
  2. 预留额度:max_connections 不是全给业务的——超级用户预留连接必须留出,否则故障时连管理员都进不来。
  3. 安全水位:常态使用率超过 80% 就该规划扩容(提高连接池复用率或调整 max_connections),而不是等打满。连接治理见《PgBouncer 连接池》。

pg_stat_slru:SLRU 缓存统计(PG13+) ​

PostgreSQL 有一部分磁盘数据不走 shared_buffers,而是走一组专用的 SLRU(simple least-recently-used)缓存:子事务状态、提交时间戳、通知队列、多事务状态等。PG13 起这些缓存有独立统计视图 pg_stat_slru,每个 SLRU 一行:

字段含义
blks_hit / blks_readSLRU 缓存的命中与读盘次数
blks_written写出(换出)的块数
blks_exists「检查存在性」类访问的次数
flushes缓存页被刷写的次数
truncates缓存被截断(重置)的次数
stats_reset统计重置时间
sql
-- 各 SLRU 的命中率一览
SELECT name,
       blks_hit,
       blks_read,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct,
       flushes,
       truncates
FROM pg_stat_slru
WHERE blks_hit + blks_read > 0
ORDER BY blks_read DESC;

观察价值:

  • 子事务类 SLRU 命中率低、blks_read 持续走高,通常对应 SAVEPOINT 密集的应用,是旧版本无法调参、只能靠应用侧减负的隐性瓶颈。
  • PG17 起部分 SLRU 缓存的大小可用专用 GUC 调整,命中率数据就是调参前后对比的依据;具体参数名以所用版本文档为准。
  • name 列的具体取值随版本略有出入(内核增删过若干 SLRU),巡检脚本按行读取、不要硬编码名称。

检查点统计详解 ​

检查点是库级健康里最容易「滞后暴露」的问题:配置不当不会立刻报错,而是慢慢表现为周期性 IO 抖动与长尾延迟。PG17 把检查点统计拆到 pg_stat_checkpointer,PG16 及以前在 pg_stat_bgwriter 里,字段语义一致:

字段含义观察点
checkpoints_timed按计划(checkpoint_timeout)触发的检查点应占绝大多数
checkpoints_req因 WAL 量达到 max_wal_size 被迫触发的检查点占比高说明 max_wal_size 偏小
checkpoint_write_time检查点写缓冲阶段的累计耗时配合 checkpoint_completion_target 评估摊销
checkpoint_sync_time检查点 fsync 阶段的累计耗时fsync 尖峰对应 IO 抖动
buffers_written检查点写出的缓冲块总数与 WAL 速率联动看
sql
-- 检查点节奏健康度(PG17+;PG16 及以前把表名换成 pg_stat_bgwriter)
SELECT checkpoints_timed,
       checkpoints_req,
       round(100.0 * checkpoints_req /
             nullif(checkpoints_timed + checkpoints_req, 0), 1) AS req_pct
FROM pg_stat_checkpointer;

req_pct 长期超过 20%~30% 就是「WAL 生成速度超过了检查点预算」的信号,处理路径(调 max_wal_size、找出 WAL 生成大户)见《WAL 与检查点优化》。

分钟级采样落库:完整实现 ​

把「累计值 → 速率」的换算固化成一个可复用的采样方案:

sql
-- 历史表(一次采样 = 一行累计值)
CREATE TABLE pg_stat_db_history (
    captured_at   timestamptz NOT NULL DEFAULT now(),
    datname       text NOT NULL,
    xact_commit   bigint,
    blks_read     bigint,
    blks_hit      bigint,
    temp_bytes    bigint,
    deadlocks     bigint,
    sessions      bigint,
    PRIMARY KEY (captured_at, datname)
);

-- 采样:把当前累计值写入(周期 1 分钟)
INSERT INTO pg_stat_db_history
    (datname, xact_commit, blks_read, blks_hit, temp_bytes, deadlocks, sessions)
SELECT datname, xact_commit, blks_read, blks_hit, temp_bytes, deadlocks, sessions
FROM pg_stat_database
WHERE datname = current_database();

-- 报表:用窗口函数把相邻两次采样算成速率
SELECT captured_at,
       xact_commit - lag(xact_commit) OVER (ORDER BY captured_at) AS commits_per_min,
       round((blks_hit - lag(blks_hit) OVER (ORDER BY captured_at))::numeric /
             nullif((blks_hit - lag(blks_hit) OVER (ORDER BY captured_at)) +
                    (blks_read - lag(blks_read) OVER (ORDER BY captured_at)), 0) * 100, 2) AS hit_pct_per_min,
       pg_size_pretty((temp_bytes - lag(temp_bytes) OVER (ORDER BY captured_at))::bigint) AS temp_per_min
FROM pg_stat_db_history
WHERE datname = current_database()
  AND captured_at >= now() - interval '1 day'
ORDER BY captured_at;

历史表同样按保留期清理(如 90 天),由 cron 或监控系统驱动采样。更通用的采样模板(任意指标接入)见《常用监控 SQL 汇总》。

基线与分位数告警 ​

有了历史数据,告警阈值就能从「拍脑袋」升级为「从基线推导」:

sql
-- 从 30 天历史推导命中率与事务速率的常态区间(分位数)
SELECT round(percentile_cont(0.5) WITHIN GROUP (ORDER BY hit_pct_per_min), 2) AS hit_p50,
       round(percentile_cont(0.05) WITHIN GROUP (ORDER BY hit_pct_per_min), 2) AS hit_p05,
       round(percentile_cont(0.95) WITHIN GROUP (ORDER BY commits_per_min)) AS commits_p95
FROM (
    SELECT xact_commit - lag(xact_commit) OVER (ORDER BY captured_at) AS commits_per_min,
           (blks_hit - lag(blks_hit) OVER (ORDER BY captured_at))::numeric /
           nullif((blks_hit - lag(blks_hit) OVER (ORDER BY captured_at)) +
                  (blks_read - lag(blks_read) OVER (ORDER BY captured_at)), 0) * 100 AS hit_pct_per_min
    FROM pg_stat_db_history
    WHERE datname = current_database()
      AND captured_at >= now() - interval '30 days'
) t;

告警规则的建议形态:命中率跌破 P05(而不是某个绝对数字)、事务速率超过 P95 持续 N 分钟、deadlocks 与 datchecksum_failures 增量 > 0 即告警。分位数随业务漂移自动校准,比固定阈值少一半误报。

pg_stat_io:统一 I/O 视图速览(PG16+) ​

PG16 引入的 pg_stat_io 把「谁在读什么」从库级下钻到「后端类型 × 上下文 × 对象」的组合维度,是本页命中率指标的理想补充:

sql
-- 全表扫描读盘 vs 常规读盘的区分(库级命中率看不到的差异)
SELECT backend_type, context,
       sum(reads)  AS reads,
       sum(hits)   AS hits,
       sum(writes) AS writes,
       sum(evictions) AS evictions
FROM pg_stat_io
GROUP BY backend_type, context
ORDER BY reads DESC
LIMIT 10;

它最有诊断价值的三组维度:context = 'normal' 与 'vacuum' / 'bulkread' 分开的读盘(表膨胀导致的读盘会伪装成「命中率低」,在这里现形);evictions / reuses 列暴露缓冲区的驱逐与复用压力;fsyncs 联系检查点节奏。字段语义与调参指引见《shared_buffers 共享缓冲区》。

实战示例 ​

示例一:每日巡检看板 ​

sql
SELECT d.datname,
       d.numbackends AS conns,
       d.xact_commit,
       round(100.0 * d.xact_rollback /
             nullif(d.xact_commit + d.xact_rollback, 0), 2) AS rollback_pct,
       round(100.0 * d.blks_hit / nullif(d.blks_hit + d.blks_read, 0), 2) AS hit_ratio,
       pg_size_pretty(d.temp_bytes) AS temp_written,
       d.deadlocks,
       d.sessions,
       round(100.0 * d.sessions_abandoned / nullif(d.sessions, 0), 2) AS abandoned_pct,
       d.stats_reset::date AS stats_since
FROM pg_stat_database d
WHERE d.datname NOT IN ('template0', 'template1')
  AND d.datname IS NOT NULL
ORDER BY d.xact_commit DESC;

输出即巡检表:命中率低于 99%(OLTP)、回滚率高于 5%、临时文件持续增长、abandoned 比例上升——任何一格异常都值得跟进。

示例二:临时文件增长告警与下钻 ​

sql
-- 库级:采样对比 temp_bytes 增量
SELECT datname,
       temp_files,
       pg_size_pretty(temp_bytes) AS temp_total
FROM pg_stat_database
WHERE temp_bytes > 0;

-- 下钻:pg_stat_statements 找到制造临时文件的语句
SELECT temp_blks_written,
       pg_size_pretty((temp_blks_written *
                       current_setting('block_size')::bigint)::bigint) AS temp_written,
       calls,
       left(query, 100) AS query_head
FROM pg_stat_statements
ORDER BY temp_blks_written DESC
LIMIT 10;

处置顺序:先看语句能否优化(排序去重的列上加索引、避免 SELECT DISTINCT 大结果集),再考虑按角色调大 work_mem。

示例三:备库冲突监控 ​

sql
-- 备库上执行:恢复冲突是否在增长
SELECT datname,
       confl_snapshot,
       confl_lock,
       confl_deadlock
FROM pg_stat_database_conflicts;

confl_snapshot 增长时,日志会同步出现 canceling statement due to conflict with recovery。结合《pg_stat_replication 监控》一页判断回放延迟,再决定开启 hot_standby_feedback 还是调整 max_standby_streaming_delay。

示例四:用 pg_stat_io 判断缓存是否够用 ​

sql
SELECT backend_type,
       sum(hits) AS hits,
       sum(reads) AS reads,
       round(100.0 * sum(hits) / nullif(sum(hits) + sum(reads), 0), 2) AS hit_pct,
       sum(evictions) AS evictions
FROM pg_stat_io
WHERE backend_type = 'client backend'
GROUP BY backend_type;

evictions 持续偏高说明 shared_buffers 频繁驱逐——在工作集可预估的前提下,参考《shared_buffers 共享缓冲区》一页调整容量,并用本查询的前后对比验证效果。

示例五:SLRU 命中率观察 ​

旧版本 SLRU 大小不可调,但统计照样有诊断价值——它告诉你「子事务密集」这类隐性负载的存在:

sql
SELECT name, blks_hit, blks_read,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct
FROM pg_stat_slru
ORDER BY blks_read DESC;
text
       name       | blks_hit | blks_read | hit_pct
------------------+----------+-----------+---------
 subtransaction   |  1820441 |    840223 |   68.42
 commit_timestamp |    20413 |        12 |   99.94
 multixact_offset |      842 |         4 |   99.53
(3 rows)

subtransaction 命中率不到七成、读盘几十万次——结合 pg_stat_activity 里 SAVEPOINT 密集的会话,可以确认应用在大量使用子事务。PG17+ 环境的处置路径是调整对应 SLRU 的 GUC;旧版本的缓解手段是应用侧减少嵌套 SAVEPOINT、合并短事务。

示例六:采样函数与增量报表 ​

把采样逻辑封装成函数,cron 一行调用:

sql
CREATE OR REPLACE FUNCTION fn_snap_pg_stat_db() RETURNS void AS $$
    INSERT INTO pg_stat_db_history
        (datname, xact_commit, blks_read, blks_hit, temp_bytes, deadlocks, sessions)
    SELECT datname, xact_commit, blks_read, blks_hit, temp_bytes, deadlocks, sessions
    FROM pg_stat_database
    WHERE datname IS NOT NULL;
$$ LANGUAGE sql;

SELECT fn_snap_pg_stat_db();
bash
# crontab:每分钟采样,每日清理 90 天前的数据
* * * * * psql -d appdb -c "SELECT fn_snap_pg_stat_db();"
30 3 * * * psql -d appdb -c "DELETE FROM pg_stat_db_history WHERE captured_at < now() - interval '90 days';"

落地要点:函数用 LANGUAGE sql 保持极简;采样账号只需 pg_monitor(或 pg_read_all_stats)与历史表的 INSERT 权限;数据量大时历史表按天分区,量小时定期 DELETE 即可。

注意事项 ​

库级累计值必须换算成速率才有意义。 直接给 xact_commit 设绝对阈值告警是错的;正确做法是周期采样求增量,按「每分钟事务数」观察趋势。

命中率不包含操作系统页缓存。 blks_read 之后还有 OS 缓存兜底,所以命中率 95% 的库实际磁盘读可能远低于 5%;反之,命中率 99% 不代表存储无压力。把 pg_stat_io 的 read_time 结合起来看更准。

numbackends 是瞬时值。 它不累计,是查询那一刻的连接数,与 pg_stat_activity 计数口径一致(都按数据库划分)。

会话统计(PG14+)从实例启动起累计。 没有「按天」的会话质量数据,需要监控系统按周期采样增量。sessions_abandoned 单日突增通常对应应用发布或网络故障。

datchecksum_failures 需要 checksums 已开启。 该功能在 initdb 时决定(--data-checksums),没开的实例此列恒为 NULL;PG12 及更早版本没有这两列。

pg_stat_io 需要时间字段就开 track_io_timing。 PG16 起 track_io_timing = on 才会填充 read_time/write_time 等列;只看次数不看时间,判断不了 I/O 是否成为瓶颈。

stats_reset 按数据库独立。 重置 A 库统计不影响 B 库;跨库对齐报告周期时要逐库核对 stats_reset。

临时文件统计包含所有场景。 排序溢出、哈希溢出、物化、游标Materialize 都计入 temp_bytes;它只回答「有多少」,回答「谁干的」要下钻到 pg_stat_statements 或 pg_stat_io 的 temp 维度。

最佳实践 ​

  1. 把 pg_stat_database 采样进监控系统,而不是人肉查。 每分钟采一次增量,输出事务速率、命中率、临时文件速率、会话质量四组曲线,这是所有容量规划与故障复盘的底座。
  2. 为每个库建立命中率基线。 记录常态值并告警「相对基线的下降幅度」而不是绝对阈值;不同库(OLTP/报表)的合理命中率差异巨大。
  3. deadlocks 与 datchecksum_failures 设为零容忍告警。 死锁增长意味着代码问题,校验和失败意味着存储问题——两者都不该沉默。
  4. 用会话统计治理连接。 sessions_abandoned 比例与 idle_in_transaction_time 占比是应用侧连接管理的体检报告,按周回顾并反馈给应用团队。
  5. 备库巡检必查 pg_stat_database_conflicts。 冲突计数增长与复制延迟要放在一起解读,治理手段在《pg_stat_replication 监控》一页。
  6. I/O 问题先用 pg_stat_io 分流,再下钻到表和语句。 「业务读盘多」查缓存与索引,「vacuum 场景读盘多」是正常清理,「checkpointer 写盘多」查检查点配置——不同场景不同药方。
  7. 临时文件速率与 pg_stat_statements 临时块榜联动。 库级指标发现异常,扩展统计定位语句,形成「发现 → 定位」的闭环。
  8. 报表里永远带上 stats_reset。 累计值没有周期就没有语义;这也是判断「统计是否被人为重置过」的审计依据。
  9. PG17+ 把检查点统计从 bgwriter 视角独立出来看。 checkpoints_req 与 checkpoints_timed 的比值是 WAL 与检查点参数调优的直接反馈,详见《WAL 与检查点优化》一页。
  10. 大版本升级后重置基线并重新积累。 统计随升级清零,把升级日作为新的统计起点,一周后重建各类基线。
  11. 巡检 SQL 做成幂等脚本入库管理。 本页的看板查询、下钻查询与采样表结构都应进入版本库,避免「只有某个 DBA 会跑」的知识孤岛。

下一节

继续学习:常用监控 SQL 汇总

参考链接 ​