跳到正文

PgSQL 监控指标与告警 ​

一句话:PostgreSQL 的雪崩通常不是某条 SQL 突然变慢,而是长事务或复制槽卡住 WAL 回收、死元组拖垮 autovacuum、连接耗尽后重试放大;盯住这三条慢变量,比盯 QPS 更早拿到处置窗口。适用版本:PostgreSQL 13+ / 16+,阈值按基线校准。

为什么需要 ​

PostgreSQL 的统计视图大多是累计值,重启或 pg_stat_reset() 后清零;直接拿绝对值设阈值会误报,必须看单位时间增量。更关键的是,它的故障往往先体现在「空间与回收」而不是「CPU」:WAL 不回收会写满磁盘,死元组不回收会让表和索引膨胀,idle in transaction 会同时拖住锁和 vacuum。因此监控要分清结果指标和先兆指标。P99、错误率是结果;复制槽保留的 WAL、idle in transaction 时长、死元组比例、checkpoint 频率是先兆。下面按连接、库表、复制/WAL、checkpoint、锁、SQL 扩展分层,最后串成雪崩链。

核心机制 ​

连接:进程模型决定了上限 ​

state含义风险
active正在执行查询结合 wait_event_type 判断在等锁、等 IO 还是等客户端
idle空闲,不在事务中本身无害,但连接池泄漏会堆高总数
idle in transaction开了事务但没提交持有快照和锁,阻塞 vacuum,最容易拖出膨胀和 DDL 排队

max_connections 默认 100,另有 superuser_reserved_connections 默认 3 个保留位。每个连接是独立进程,连接数受内存和进程调度约束;idle_in_transaction_session_timeout 默认 0(不超时)。连接耗尽的路径是:查询变慢 → 连接占用变长 → 达到上限报 too many clients → 应用重试 → 新连接更多,形成正反馈。

数据库与表:命中率、死元组、扫描 ​

  • pg_stat_database:blks_hit 与 blks_read 算共享缓冲命中率;deadlocks 看死锁;temp_bytes 看排序/哈希落盘;xact_rollback 与 xact_commit 算回滚比例。
  • pg_stat_user_tables:n_dead_tup、last_autovacuum、seq_scan / idx_scan。死元组比例用 n_dead_tup / (n_live_tup + n_dead_tup);last_autovacuum 过旧说明回收没跟上。autovacuum 默认在「死元组超过 20% + 50 行」时触发(autovacuum_vacuum_scale_factor=0.2,autovacuum_vacuum_threshold=50),大表 20% 可能是几千万行,必须按表调小比例。
  • idle in transaction 持有 xmin,快照还需要看到的旧版本无法被 vacuum 回收;长事务不结束,vacuum 做了也白做。精确膨胀用 pgstattuple,近似用 pgstattuple_approx,清理优先 pg_repack,不要在高峰做 VACUUM FULL。

复制与 WAL:最容易把磁盘写满 ​

  • pg_stat_replication 看 state、write_lag、flush_lag、replay_lag,以及 pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) 的字节差。replay_lag 是时间,字节差更适合判断「还差多少」。
  • pg_replication_slots 看 active、restart_lsn、wal_status、safe_wal_size,用 pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) 算保留的 WAL。槽不消费,WAL 就一直留着。
  • max_slot_wal_keep_size(13+,默认 -1 不限制)是保险丝:超过后槽会失效(wal_status=lost),订阅端会断。设置它是拿「复制连续性」换「磁盘安全」,必须明确取舍。
  • 归档失败同样会留住 WAL:pg_stat_archiver 的 failed_count、last_failed_wal、last_failed_time 持续增长,pg_wal 就会涨。max_wal_size 是软限制,不能阻止复制槽或归档需要的 WAL 继续堆积。

checkpoint 与 IO:抖动来自集中刷脏 ​

pg_stat_bgwriter 在 13–16 提供 checkpoint 与 bgwriter 计数;17 起 checkpoint 统计拆到 pg_stat_checkpointer,以版本文档为准。16+ 的 pg_stat_io 按 backend_type、object、context 看读写、命中、eviction 和 fsync,能区分是 checkpointer、client backend 还是 autovacuum 在打 IO。checkpoint_timeout 默认 5 分钟,max_wal_size 默认 1GB,checkpoint_completion_target 在 14+ 默认 0.9(13 为 0.5)。WAL 生成超过 max_wal_size 会触发 requested checkpoint;脏页集中刷盘,await 抖动,查询 P99 跟着上升。buffers_backend_fsync > 0 说明后台写不过来、客户端进程被迫自己 fsync,是 IO 压力的强信号。

锁与 SQL 扩展:把阻塞链和慢 SQL 落到会话 ​

  • pg_locks 要与 pg_stat_activity 关联看;pg_blocking_pids(pid) 直接返回阻塞它的 PID 数组。DDL 取 ACCESS EXCLUSIVE,它排队后,后续读请求也可能排在它后面,表现为整表查询突然卡住。lock_timeout 默认 0(不超时),deadlock_timeout 默认 1 秒;应用侧要设语句和事务超时,不能只靠数据库。
  • pg_stat_statements 需要 shared_preload_libraries 加 CREATE EXTENSION;13+ 用 total_exec_time / mean_exec_time,更早版本是 total_time。track_io_timing 默认关闭时,blk_read_time / blk_write_time 为 0。pg_stat_progress_vacuum 可看 vacuum 进度。

雪崩链路 ​

左边是空间链:WAL 回收被卡住后,磁盘写满会让实例 PANIC 或宕机,这是最短的雪崩路径。右边是回收与连接链:死元组让扫描变慢,连接被占满后重试又反过来制造长事务。下方 checkpoint 是加速器,它不会单独致命,但会把已经紧张的 IO 推过阈值。

最小上手 ​

sql
-- 连接按状态和等待事件聚合
SELECT state, wait_event_type, wait_event, count(*)
FROM pg_stat_activity WHERE backend_type = 'client backend'
GROUP BY 1,2,3 ORDER BY 4 DESC;

-- 长事务与 idle in transaction
SELECT pid, datname, state, now() - xact_start AS xact_age,
       now() - state_change AS state_age, wait_event_type, wait_event, left(query, 80) AS query
FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_age DESC LIMIT 10;

-- 复制延迟与槽保留的 WAL
SELECT application_name, state, sync_state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_bytes,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication;
SELECT slot_name, active, wal_status,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
SELECT archived_count, failed_count, last_archived_time, last_failed_time FROM pg_stat_archiver;

-- 死元组、扫描方式与阻塞链
SELECT relname, n_live_tup, n_dead_tup,
       round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_pct,
       seq_scan, idx_scan, last_autovacuum
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;
SELECT pid, pg_blocking_pids(pid) AS blockers, wait_event_type, wait_event, left(query, 80) AS query
FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;
promql
# 共享缓冲命中率
100 * sum(rate(pg_stat_database_blks_hit[5m])) / clamp_min(sum(rate(pg_stat_database_blks_hit[5m])) + sum(rate(pg_stat_database_blks_read[5m])), 1)

# 连接使用率(指标名随 postgres_exporter 版本和 collector 变化,以 /metrics 为准)
100 * sum(pg_stat_database_numbackends) / scalar(pg_settings_max_connections)

生产动作 ​

告警起点阈值说明
连接使用率超过 max_connections 的 80%,持续 5 分钟先查泄漏和 idle in transaction,不要只调大上限
长事务事务打开超过 5 分钟;idle in transaction 超过 60 秒它同时拖住锁、快照和 vacuum
锁等待阻塞会话超过 5 秒,或 wait_event_type='Lock' 数量超基线用 pg_blocking_pids() 找根节点,不要杀所有等待者
复制延迟replay_lag > 30s、字节差持续增长,或 state != streaming秒数之外还要看 WAL 字节差
复制槽槽 inactive 超过 10 分钟,或保留 WAL 超过 10GB起点值;确认订阅端是否真的下线再处理
WAL 与磁盘pg_wal 超基线、磁盘超过 80%、归档失败计数增长归档失败和槽滞留是两条独立原因
死元组单表死元组比例超过 20%,且 last_autovacuum 超过 24 小时大表要按表调阈值,不能只看全局默认
checkpoint 与临时文件requested checkpoint 频率超基线、buffers_backend_fsync > 0,或 temp_bytes 增量突增结合 pg_stat_io 看谁在写;临时文件突增常是排序/哈希落盘或锁冲突

处置顺序默认是「先保护连接和 WAL,再处理膨胀与锁,最后扩容」:

  1. 保护入口:终止明确的 idle in transaction,设置 statement_timeout、lock_timeout、idle_in_transaction_session_timeout;连接池满先修池,不要只加 max_connections。
  2. 止住 WAL 增长:先修归档;对 inactive 槽,确认订阅端/备库确实不再需要后才能删除,绝不能删 active 槽;把 max_slot_wal_keep_size 当作保险丝而不是日常回收手段。
  3. 处理锁与膨胀:杀掉阻塞链根节点的事务;膨胀优先 pg_repack 或错峰 vacuum,不在高峰 VACUUM FULL。
  4. 定位与扩容:用 pg_stat_statements 和 pg_stat_io 判断读放大、写放大还是 checkpoint;读瓶颈加副本/缓存,写瓶颈分区或分片,checkpoint 参数调整要结合 WAL 生成率验证。

边界 ​

  • 统计视图是累计值或估算值:n_dead_tup 是估算,精确值用 pgstattuple;pg_stat_statements 需要 preload,未加载时查不到。
  • replay_lag 受时钟和网络影响;跨机房或同步复制下,要结合 LSN 字节差和业务读一致性判断。
  • 复制槽是「保留 WAL」的承诺,不是备份;槽删了 WAL 可能立刻回收,但订阅端也可能永久断档。
  • max_wal_size 不是硬上限,归档或槽需要时会继续增长,磁盘告警必须独立于它。监控也替代不了业务指标,用户侧 P99、错误率和数据新鲜度才是最终判据。

优缺点与扬长避短 ​

优点是扩展性强:pgvector、PostGIS、JSONB、全文检索都能作为普通列和普通索引参与事务;SQL 标准和复杂查询强(CTE、窗口、LATERAL、并行查询);复制形态多(物理/逻辑、同步/异步、级联),DDL 事务化。代价是 autovacuum 和 bloat 必须长期治理;没有聚簇索引(CLUSTER 是一次性且锁表,之后不维持顺序);复制槽与 WAL 运维有坑;每连接一个进程,连接成本高;超高写入吞吐仍要靠分片方案。

约束条件判断说明
业务库同时要 JSONB、全文、地理或向量适用扩展与业务数据共享事务,少一套同步链路
复杂报表、窗口、CTE、并行查询适用优化器和 SQL 能力更合适
需要逻辑复制、多订阅或 CDC适用逻辑复制形态完整,但要管槽和 WAL
超高写入、海量简单 KV单实例不适用应分片或换专用系统,不是调参能解决
团队没有 vacuum/bloat 治理能力不适用需要托管服务或长期治理机制
连接数极高且没有连接池不适用进程模型成本高,先用 PgBouncer

与 MySQL 按约束对比:单主点查和成熟托管生态,MySQL 更省心;事务内混合 JSONB/全文/地理/向量、复杂查询、逻辑复制,PgSQL 更合适;超高连接数两者都要池化,PgSQL 的进程模型更敏感;超高写入两者都要分片,MySQL 的分库分表中间件生态更常见,PgSQL 可用 Citus。结论不是谁更强,而是约束落在哪一边。

讲清楚 ​

PostgreSQL 监控先看三条慢变量:WAL 能不能回收、死元组能不能被 vacuum 回收、连接有没有被慢查询和重试占满。WAL 侧盯 pg_stat_replication 的 lag、pg_replication_slots 保留的 WAL、pg_stat_archiver 的失败计数和 pg_wal 目录大小;回收侧盯 pg_stat_user_tables 的死元组比例和 last_autovacuum,并用 pg_blocking_pids() 找锁阻塞根节点;连接侧按 pg_stat_activity 的 state 和 wait_event 聚合,重点抓 idle in transaction。雪崩链是「长事务/未消费槽 → WAL 或死元组积压 → 磁盘满或表膨胀 → P99 上升 → 连接耗尽 → 重试放大」。处置先保护连接和 WAL,再处理膨胀与锁,最后扩容;复制槽、autovacuum 和连接池是长期治理项,不是一次调参。

把一门技术讲透到既懂也会用