数据库告警时,你希望第一反应是"打开仪表盘快速定位",而不是"登录服务器逐个猜"。一套完整的 PostgreSQL 监控体系,核心由三部分构成:系统视图(pg_stat_*)、日志与执行计划(慢查询/auto_explain)、外部指标采集(Prometheus/Grafana)。
核心认知:监控不是"收集数据",而是"压缩信息"。从 pg_stat_activity 的会话视图,到 pg_stat_statements 的语句级 Top N,再到 Prometheus 的长期指标,层级越高,越接近"能直接定位根因"。
一、监控体系概览
1.1 三层监控架构
监控体系分三层:应用层(连接池/PgBouncer、错误率、响应延迟)、数据库层(pg_stat_* 视图、日志、SQL 统计)、采集层(postgres_exporter → Prometheus → Grafana/Alertmanager)。
1.2 关键监控对象清单
| 对象 | 核心指标 | 数据来源 |
|---|---|---|
| 会话 | 连接数、state、wait_event | pg_stat_activity |
| 数据库 | 事务、命中率、死锁 | pg_stat_database |
| 语句 | Top N、执行时间、缓冲区 | pg_stat_statements |
| 表 | 扫描方式、死元组 | pg_stat_user_tables |
| 复制 | 延迟、槽位 | pg_stat_replication / pg_stat_subscription |
| 资源 | CPU、内存、磁盘、IO | 操作系统层 |
二、pg_stat_database 关键指标
2.1 核心字段
SELECT * FROM pg_stat_database WHERE datname = current_database();
| 字段 | 含义 | 健康参考 |
|---|---|---|
numbackends | 当前连接数 | 低于 max_connections 的 80% |
xact_commit | 已提交事务数 | 持续增长 |
xact_rollback | 回滚事务数 | 比例应低 |
blks_read | 从磁盘读的块数 | 命中率高 |
blks_hit | 缓存命中的块数 | 命中率 > 99% |
deadlocks | 死锁次数 | 趋近 0 |
temp_files | 临时文件数 | 频繁出现说明 work_mem 不足 |
temp_bytes | 临时文件字节 | 过大说明排序/哈希溢出到磁盘 |
2.2 缓存命中率计算
SELECT
datname,
round(100.0 * blks_hit / GREATEST(blks_hit + blks_read, 1), 2) AS cache_hit_ratio
FROM pg_stat_database;
-- 命中率低于 99% 通常说明 shared_buffers 偏小或全表扫描过多
2.3 回滚率与临时文件
-- 高回滚率往往是应用层异常/事务冲突
SELECT datname,
xact_commit, xact_rollback,
round(100.0 * xact_rollback / GREATEST(xact_commit + xact_rollback, 1), 2) AS rollback_pct,
temp_files, pg_size_pretty(temp_bytes) AS temp_used
FROM pg_stat_database
WHERE datname NOT IN ('postgres', 'template0', 'template1')
ORDER BY rollback_pct DESC;
提示:
temp_files频繁增长说明work_mem设置过小,排序/哈希被写到磁盘,性能会大幅下降。
三、pg_stat_activity 会话分析
3.1 会话状态与等待事件
SELECT pid, usename, datname, state,
wait_event_type, wait_event,
query_start, xact_start,
left(query, 60) AS query
FROM pg_stat_activity
WHERE datname IS NOT NULL
ORDER BY query_start;
state 取值:active(正在执行)、idle(空闲)、idle in transaction(事务内空闲,高危)、idle in transaction (aborted)(事务已中断未回滚)。
3.2 等待事件定位
PostgreSQL 9.6+ 提供 wait_event_type / wait_event,精准描述进程在等什么:
| wait_event_type | 常见事件 | 含义 |
|---|---|---|
Lock | transactionid / relation | 等锁 |
IO | DataFileRead / WALWrite | 磁盘 IO 瓶颈 |
Activity | AutoVacuumMain | 后台进程活动 |
Client | ClientRead | 等待应用发来数据 |
Extension | 自定义扩展等待 | 扩展锁/IO |
-- 按等待事件聚合,定位系统瓶颈
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE state = 'active'
GROUP BY wait_event_type, wait_event
ORDER BY count(*) DESC;
3.3 常见会话诊断 SQL
-- 1. 找出运行最久的查询
SELECT pid, now() - query_start AS dur,
state, wait_event_type, wait_event,
query
FROM pg_stat_activity
WHERE state = 'active' AND query_start IS NOT NULL
ORDER BY query_start ASC LIMIT 20;
-- 2. 找出 idle in transaction(见事务专题)
SELECT pid, state, now() - state_change AS idle_for,
left(query, 50) AS query
FROM pg_stat_activity
WHERE state LIKE 'idle in transaction%'
ORDER BY state_change ASC;
四、慢查询日志与 auto_explain
4.1 log_min_duration_statement
-- 记录执行超过 1 秒的所有语句
ALTER SYSTEM SET log_min_duration_statement = '1000ms';
ALTER SYSTEM SET log_destination = 'stderr';
SELECT pg_reload_conf();
日志输出示例:
2026-09-27 10:00:01 UTC [12345] LOG: duration: 2345.123 ms statement: SELECT * FROM orders WHERE user_id = 1001 ORDER BY created_at DESC
4.2 慢查询日志分析
慢查询日志可配合 grep/awk 提取 duration: 行并按耗时排序,快速得到 Top N。更系统的语句级分析仍推荐 pg_stat_statements(见第五章)。
4.3 auto_explain:自动记录执行计划
auto_explain 能在慢查询触发时自动把 EXPLAIN 计划写进日志,免去"复现慢查询"的痛苦:
-- 需在共享库中加载(改后重启)
ALTER SYSTEM SET shared_preload_libraries = 'auto_explain';
ALTER SYSTEM SET auto_explain.log_min_duration = '1000ms';
ALTER SYSTEM SET auto_explain.log_analyze = 'on';
ALTER SYSTEM SET auto_explain.log_buffers = 'on';
ALTER SYSTEM SET auto_explain.log_nested_statements = 'on';
-- 重启后生效
4.4 会话级开启(无需重启)
-- 当前会话手动开启(调试时非常有用)
LOAD 'auto_explain';
SET auto_explain.log_min_duration = '100ms';
SET auto_explain.log_analyze = 'on';
-- 之后执行的慢查询会自动输出执行计划到日志
| auto_explain 参数 | 默认 | 说明 |
|---|---|---|
log_min_duration | -1(关闭) | 超过该毫秒数记录计划 |
log_analyze | off | 使用 EXPLAIN ANALYZE |
log_buffers | off | 记录 buffer 使用 |
log_nested_statements | off | 记录嵌套语句 |
log_format | text | 可设 json 便于采集 |
五、pg_stat_statements 语句级统计
5.1 启用 pg_stat_statements
-- 需重启生效
ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';
ALTER SYSTEM SET pg_stat_statements.max = 5000;
ALTER SYSTEM SET pg_stat_statements.track = 'top';
-- 重启后
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
5.2 Top 慢语句分析
-- 按总耗时排序的 Top 语句
SELECT
round(total_exec_time::numeric, 2) AS total_ms,
round(mean_exec_time::numeric, 2) AS avg_ms,
calls,
rows,
round(100.0 * shared_blks_hit /
GREATEST(shared_blks_hit + shared_blks_read, 1), 2) AS hit_ratio,
left(query, 80) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
5.3 按平均耗时与调用次数
-- 平均耗时最长的 Top 10
SELECT round(mean_exec_time::numeric,2) AS avg_ms,
calls, rows,
left(query, 80) AS query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC LIMIT 10;
-- 调用次数最多(可能是热点语句)
SELECT calls, round(total_exec_time::numeric,2) AS total_ms,
round(mean_exec_time::numeric,2) AS avg_ms,
left(query, 80) AS query
FROM pg_stat_statements
ORDER BY calls DESC LIMIT 10;
5.4 关键字段速查
| 字段 | 含义 |
|---|---|
calls | 执行次数 |
total_exec_time | 总耗时(PG13+;旧版 total_time) |
mean_exec_time | 平均耗时 |
rows | 返回行数总和 |
shared_blks_hit / shared_blks_read | 缓存命中/读盘块数 |
temp_bytes | 临时文件字节 |
blk_read_time / blk_write_time | IO 等待时间 |
建议:
pg_stat_statements是慢查询治理的主入口——先看 Top 总耗时,再下钻到 EXPLAIN,比翻日志高效得多。必要时SELECT pg_stat_statements_reset();重置统计窗口。
六、Prometheus 与 postgres_exporter
6.1 postgres_exporter 部署
postgres_exporter 是一个 Go 编写的独立采集器,把 PostgreSQL 指标暴露成 Prometheus 格式:
# docker-compose 片段
services:
postgres_exporter:
image: prometheuscommunity/postgres-exporter:latest
environment:
DATA_SOURCE_NAME: "postgresql://monitor:secret@postgres:5432/postgres?sslmode=disable"
ports:
- "9187:9187"
depends_on: [postgres]
6.2 Prometheus 抓取配置
# prometheus.yml 片段
scrape_configs:
- job_name: postgres
static_configs:
- targets: ["postgres_exporter:9187"]
6.3 常用指标
| 指标 | 含义 |
|---|---|
pg_stat_database_numbackends | 数据库连接数 |
pg_stat_database_xact_commit_total | 提交事务数 |
pg_stat_database_deadlocks_total | 死锁计数 |
pg_stat_database_blks_hit / blks_read | 缓存命中 |
pg_stat_activity_idle_in_transaction_seconds | 长事务时长 |
pg_stat_user_tables_n_dead_tup | 死元组数 |
pg_replication_lag | 复制延迟字节 |
6.4 采集注意事项
□ 创建最小权限监控角色(见下方 SQL)
□ 设置 scrape 间隔与 postgres_exporter 查询频率匹配
□ 监控大库时限制 pg_stat_statements 查询的采样
□ 为指标设置 retention 与 downsampling
-- 最小权限监控账号
CREATE ROLE monitor LOGIN PASSWORD 'secret';
GRANT CONNECT ON DATABASE mydb TO monitor;
GRANT pg_monitor TO monitor; -- PG 10+ 内置监控角色
七、Grafana 仪表盘与告警
7.1 推荐仪表盘
社区成熟的 PostgreSQL 仪表盘(Grafana 官方 / postgres_exporter 自带面板)通常包含:
| 面板 | 展示内容 |
|---|---|
| 连接数与状态 | 连接总数、state 分布 |
| 事务吞吐 | commit/rollback 速率 |
| 缓存命中率 | 全局与按库 |
| 锁等待与死锁 | 等锁会话数、死锁计数 |
| 复制状态 | 延迟、槽位 WAL 保留 |
| Top 慢语句 | pg_stat_statements 前 N |
7.2 核心告警规则
# Prometheus 告警规则(postgres_alerts.yml)
groups:
- name: postgres-critical
rules:
- alert: PostgresDown
expr: pg_up == 0
for: 1m
labels: { severity: critical }
- alert: ConnectionSaturation
expr: pg_stat_database_numbackends > (pg_settings_max_connections * 0.8)
for: 5m
labels: { severity: warning }
- alert: DeadTupleHigh
expr: pg_stat_user_tables_n_dead_tup > 10000
for: 10m
labels: { severity: warning }
- alert: ReplicationLag
expr: pg_replication_lag > 104857600 # 100MB
for: 5m
labels: { severity: critical }
- alert: IdleInTransaction
expr: pg_stat_activity_idle_in_transaction_seconds > 300
for: 2m
labels: { severity: warning }
- alert: CacheHitLow
expr: pg_stat_database_blks_hit /
(pg_stat_database_blks_hit + pg_stat_database_blks_read) < 0.99
for: 15m
labels: { severity: warning }
7.3 告警分级建议
- critical(立即告警):实例宕机、复制中断
- high(分钟级):连接打满、复制延迟持续增长
- warning(观察排查):死元组堆积、命中率下降、长事务
- info(记录趋势):临时文件增长、回滚率升高
八、诊断常用 SQL 与问题定位
8.1 一键健康体检
-- 数据库级体检
SELECT
d.datname,
d.numbackends,
round(100.0 * d.blks_hit / GREATEST(d.blks_hit + d.blks_read, 1), 2) AS hit_ratio,
d.deadlocks,
d.temp_files,
pg_size_pretty(pg_database_size(d.datname)) AS db_size
FROM pg_stat_database d
WHERE d.datname NOT IN ('postgres', 'template0', 'template1')
ORDER BY pg_database_size(d.datname) DESC;
8.2 常见问题定位方法论
现象 "数据库变慢"
├─ 1. pg_stat_activity:是否有长查询/长事务/锁等待
├─ 2. pg_stat_statements:Top 语句是否耗时长/调用多
├─ 3. EXPLAIN (ANALYZE, BUFFERS):计划是否合理、是否全表扫描
├─ 4. 资源层:CPU/IO/网络是否饱和
├─ 5. 缓存:命中率是否下降、shared_buffers 是否不足
└─ 6. 表健康:死元组/膨胀是否需要 VACUUM/REINDEX
8.3 连接打满排查
-- 连接打满时:找出占用连接的应用
SELECT usename, application_name, count(*)
FROM pg_stat_activity
GROUP BY usename, application_name
ORDER BY count(*) DESC;
-- 清理空闲连接(谨慎)
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE state = 'idle' AND now() - state_change > INTERVAL '10 minutes';
8.4 IO 与 WAL 诊断
-- 检查 WAL 写入与检查点
SELECT checkpoints_timed, checkpoints_req,
checkpoint_write_time, checkpoint_sync_time,
wal_bytes
FROM pg_stat_bgwriter;
-- 查看每个数据库的 IO
SELECT datname, blk_read_time, blk_write_time
FROM pg_stat_database
WHERE datname NOT IN ('postgres', 'template0', 'template1');
8.5 长期趋势指标清单
- 性能:事务速率、命中率、平均执行时间(30s~1min)
- 容量:库大小、表大小、WAL 增长(1min)
- 健康:死元组、膨胀率、复制延迟(1min)
- 稳定性:死锁、回滚率、连接数(1min)
- 安全:登录失败、审计事件(实时)
常见问题(FAQ)
pg_stat_statements 和慢查询日志有什么区别?
log_min_duration_statement 记录单次超阈值语句(含完整 SQL);pg_stat_statements 是聚合统计(按规范化 SQL 汇总调用次数、平均耗时),更适合找 Top 热点。两者配合:用 pg_stat_statements 找 Top,用 auto_explain/日志看单条计划的细节。
auto_explain 会影响性能吗?
会,但可控。只对超过阈值的慢语句记录计划,且 log_analyze 会真正执行分析,大库阈值建议 1~2 秒以上;调试期临时调小,生产保持谨慎。
死元组告警阈值设多少合适?
通用经验:n_dead_tup 超过 10000 或占活元组比例超过 10%~20% 就应关注。结合 last_autovacuum 看:死元组持续增长且 Autovacuum 长时间没跑,说明回收被阻塞(常见长事务),这才是告警的深意。
连接打满如何处理?
先按 application_name 分组找出连接占用来源,确认连接池是否过小或应用泄漏。临时处置可 pg_terminate_backend 清理空闲连接;根本解决是规划 max_connections、上 PgBouncer 连接池、给应用设超时。
监控账号需要多大权限?
用最小权限:CREATE ROLE monitor LOGIN + GRANT pg_monitor TO monitor(PG 10+ 内置角色,可读所有统计视图)。不要给监控账号超级用户权限;postgres_exporter 用它抓取即够。
相关阅读
- PostgreSQL 查询优化实战 — EXPLAIN ANALYZE 深度解读与慢查询治理闭环
- PostgreSQL 性能调优 — 参数配置、连接池、Autovacuum 调优
- PostgreSQL VACUUM 与表膨胀治理 — 死元组监控与告警
- PostgreSQL 事务、隔离级别与锁 — 锁等待与长事务诊断
- PostgreSQL Docker 部署与初始化 — Grafana 监控容器化部署
- PostgreSQL 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。