周五晚上十点,某客户的生产 PostgreSQL 实例 CPU 突然飙到 95%,连接数告警连着弹了三条。这种时候谁来得及临时搭一套监控平台?第一反应肯定是 psql 连上去,把 pg_stat_activity、pg_stat_database、pg_stat_bgwriter 挨个查一遍,再手算几个比率。做是能做,但十几个视图翻下来,一个问题没查完,半天就过去了。后来我在 GitHub 上翻到一个叫 pgmetrics 的开源小工具,一条命令把数据库里里外外的统计指标全部拉出来,文本、JSON 都能出,不带网页、不带 agent、不常驻内存。用了一段时间之后,它已经成了我排查 PostgreSQL 问题的默认起手式。
pgmetrics 是一款免费开源的 PostgreSQL 统计指标采集工具,定位很朴素:你给它一个能连上数据库的连接串,它读一遍系统视图和统计信息,然后给你一份足够全面的体检报告。适合 DBA、SRE、后端开发,也适合那些不想为一个小数据库专门搭一套 Prometheus 监控栈的人。这篇文章我把自己的使用经验拆开来讲:为什么选它、怎么跑通、指标怎么看、怎么接脚本和告警,以及在真实环境里踩过的权限和连接坑。
1. 为什么我最终选了 pgmetrics:聊聊常规监控的三个尴尬
1.1 手写 SQL 查询视图:灵活但是费人
PostgreSQL 本身不缺监控数据,pg_stat_database、pg_stat_activity、pg_stat_all_tables、pg_stat_replication、pg_stat_archiver 这些视图已经把家底摆在那了。问题是数据分散,要拼出一份“数据库健康状况总览”得写一长串 SQL,而且每次排查问题都要现写,很难沉淀。
我一开始也是手工派,遇到问题就噼里啪啦敲查询,用完 Ctrl+C 扔掉。时间一长发现几个痛点:一是不同版本的 PostgreSQL 系统视图列名有差异,脚本要维护兼容;二是为了算一个缓存命中率,得自己写 SUM(blks_hit) * 1.0 / (SUM(blks_hit) + SUM(blks_read)) 这种比例,查多了容易烦;三是没有任何历史记录,今天看到的数据和昨天的对不上,想判断“是不是恶化了”只能靠直觉。
并不是说手写 SQL 没用。恰恰相反,pgmetrics 的很多输出其实就是把这些 SQL 预置好了。对于临时深挖某个具体问题,我还是会自己写 SQL 去追,比如查某个 query 的等待事件,看 pg_stat_statements 里的 top SQL。但如果是想做一次实例级巡检,或者接到告警后想尽快知道“这个库到底怎么了”,手写 SQL 的效率就太低了。
1.2 部署 exporter 接入 Prometheus:好方案但有点重
再正规一点的路线是 Prometheus 加 postgres_exporter。这是长期监控的标准答案,exporter 按固定间隔抓取指标,Prometheus 负责存储、告警、出图,Grafana 上直接套一套现成的 dashboard。这套组合在生产环境里非常成熟,我自己的核心业务库也是这么盯的。
问题出在两个地方。第一是部署成本。一个开源项目或者客户的测试环境,可能只有一台 2C4G 的机器,上面既要做业务又要跑数据库,再塞一个 Prometheus 加 Grafana 加 exporter,资源虽然不多但维护链路长了不少。第二是临时性。很多排查场景是突发的,比如连接数告警、vacuum 异常、复制延迟变大,这时候我不想先去翻 Grafana 看有没有配这个指标,我只想立刻知道当前状态。
对比下来,pgmetrics 恰好卡在“手写 SQL”和“Prometheus 全家桶”之间:单文件二进制,不安装不常驻,连上去跑一次就能拿到结构化报告。它不替代 Prometheus,而是补上“临场排查”和“轻量巡检”这两块拼图。
1.3 云数据库自带监控:省心但不够细
云厂商的托管 PostgreSQL(RDS、CloudSQL 之类的)都自带监控面板,CPU、内存、连接数、磁盘这些基础指标做得挺漂亮。但细看会发现,表级死元组比例、索引使用情况、复制延迟细节、归档状态这些偏内核的信息,在云监控里经常没有,或者要额外付费开启增强监控。
pgmetrics 有个好处:它不管你的库跑在哪里,只要能建立 TCP 连接就能采集。云上数据库也可以跑,而且它读的都是系统视图,对托管实例同样适用。我经常用它在云数据库上补一道“现场体检”,拿到云厂商控制台上看不到的细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 五分钟跑通:下载、连接与三种输出格式
2.1 获取二进制:GitHub Release 里挑一个文件
pgmetrics 用 Go 写的,发布物就是一个压缩包,里面只有一个可执行文件,不用装依赖,不用配置环境变量。GitHub 仓库的 Release 页面会提供 linux、macOS、Windows、FreeBSD 等平台的安装包。
在 Linux 服务器上,我一般先判断架构再下载:
bash复制uname -m
# x86_64 就是 amd64,aarch64 就是 arm64
wget https://github.com/rapidloop/pgmetrics/releases/download/v1.15.0/pgmetrics_1.15.0_linux_amd64.tar.gz
tar xzf pgmetrics_1.15.0_linux_amd64.tar.gz
sudo mv pgmetrics /usr/local/bin/
pgmetrics --version
这里有个实际心得:下载之前先确认数据库服务器的架构,尤其是现在 ARM 服务器越来越常见,拿错 amd64 版本会直接跑不起来。另外 pgmetrics 支持通过 --help 查看完整参数,版本不同参数细节可能略有差异,读一遍本地文档比看网上过期教程可靠。
2.2 连接参数:沿用 psql 的使用习惯
pgmetrics 的连接参数设计得很贴近 psql,所以用过 PostgreSQL 的人基本不用重新学。最基础的一条命令:
bash复制pgmetrics -u postgres --host 127.0.0.1 --port 5432 --dbname postgres
它默认会连接本机的 5432 端口,所以如果库就在本机、端口也是默认的,可以进一步简写:
bash复制pgmetrics -u postgres --dbname postgres
密码的处理有三种方式:
- 交互输入:运行后按提示输入密码,适合手工排查;
- 环境变量:
export PGMETRICS_PASSWORD='你的密码',适合脚本里调用,避免密码出现在命令行历史里; .pgpass文件:和 psql 完全一样的机制,host:port:database:user:password格式,权限需要是 600。
我第一次用的时候习惯性敲了 -W 参数,结果报错说不认识这个选项。pgmetrics 里交互输密码不用 -W,它默认在你没设置 PGMETRICS_PASSWORD 且 .pgpass 里没有匹配项时,就会提示输入密码。这个习惯差异要注意,否则刚上手会卡一下。
如果实例开了 SSL,可以用 --sslmode 控制,常用取值是 require 和 verify-full。默认行为是优先尝试 SSL,不行再回退,和 libpq 的策略基本一致。
2.3 三种输出模式:终端、脚本、机器各取所需
pgmetrics 最常用的三种输出方式我都在用:
第一种是默认的文本模式,带 ANSI 色彩,表格式排版。字段多的部分会按屏宽换行,终端里看非常直观,适合人肉排查。颜色会标出一些重要状态,比如连接使用率高的时候会高亮。如果想把彩色输出重定向到文件,或者放到 CI 日志里,记得加 --no-color,不然文件里会全是转义字符,很伤眼睛。
第二种是 --no-color 纯文本模式,和默认文本内容一样,只是去掉颜色。适合存文件归档,也适合在没有终端富文本支持的 Web 系统里展示。
第三种是 --json 模式,输出一个完整的 JSON 文档。这个是我最喜欢的输出格式,因为它把采集结果结构化,交给 jq 或者 Python 都能直接做二次加工,后面章节我会专门写怎么用。
一个小细节:--json 模式下默认不会输出任何日志噪声,干净利落,非常适合放进管道。我在脚本里基本只会用它。
3. 指标深度拆解:这二十多项统计到底在告诉你什么
pgmetrics 输出的数据量很大,第一次用的时候容易被信息淹掉。我按自己的理解把它拆成六个维度来讲,这部分也是我认为全文最有价值的地方:知道工具能采集什么,和知道每个数字说明什么,是两回事。
3.1 数据库级指标:大小、事务、缓存命中
pgmetrics 会逐一列出每个数据库的大小、事务提交/回滚数量、缓存命中率、临时文件读写量、元组读写量、死锁次数等。
最值得先看的是缓存命中率。这个数值的意思是“数据库从共享缓冲区中直接命中数据的比例”,它由 blks_hit / (blks_hit + blks_read) 计算而来。理想情况下应该长期在 99% 以上。如果低于 95%,说明有大量读请求落到了磁盘,shared_buffers 可能偏小,或者某些查询在扫描远超缓存大小的大表。
事务回滚率也值得留意。xact_rollback / xact_commit 如果明显偏高,说明应用层可能存在大量不必要的异常事务,比如连接中断后未提交就断开、业务代码里事务嵌套出了错。它不是性能问题,但经常是应用 bug 的一面镜子。
临时文件读写量容易被忽略。temp_files 和 temp_bytes 持续增长,说明有大量查询需要写临时文件,最常见原因是 work_mem 设置太小,排序或者哈希操作溢出到了磁盘。我在 OLAP 场景下经常看到这个指标爆表,解决方案不是无脑调大 work_mem,而是先定位到具体查询,再针对性地优化 SQL 或会话级参数。
3.2 连接与会话:连接使用率、idle in transaction
连接数是 PostgreSQL 世界里最常见的“显性故障”。pgmetrics 会输出当前连接数、最大连接数、每个数据库的活跃连接数,以及会话状态分布。
连接使用率超过 80% 就要引起注意了。因为 PostgreSQL 的 max_connections 是硬上限,一旦打满,新的连接请求会直接报 FATAL: sorry, too many clients already,这是一个非常典型的故障。
会话状态分布里,我最关心的是 idle in transaction。这种会话非常阴险:事务已经开启但一直没提交,连接看起来还活着,实际上占住了一个连接槽位,还持有锁,会阻塞其他事务,甚至导致 autovacuum 无法清理死元组。pgmetrics 里如果这部分数量不为 0,我会立刻查一下来源并考虑配置 idle_in_transaction_session_timeout,让数据库自动杀掉这种僵尸事务。
3.3 锁与死锁:等待事件、死锁计数
PostgreSQL 的锁等待是另一个高频问题。pgmetrics 会展示当前是否存在等待锁的会话,以及累计死锁次数。死锁计数如果持续增长,说明应用里多个事务以不同顺序更新同一批资源,比如先更新 A 再更新 B,而另一个事务先更新 B 再更新 A。这种问题需要业务侧统一加锁顺序,靠数据库参数是治不好的。
真实排查锁问题时,我一般先用 pgmetrics 看有没有等待锁的会话,然后立刻用 psql 查 pg_blocking_pids() 找真正的锁源。pgmetrics 的价值是帮你快速确认“当前库里到底有没有锁问题”,而定位具体链路还是需要手工 SQL 配合。
3.4 VACUUM 与表健康:死元组比例
PostgreSQL 的 MVCC 机制决定了更新和删除会产生死元组,必须靠 vacuum 清理。如果 vacuum 跟不上,表会膨胀,索引效率下降,查询变慢。pgmetrics 会列出每个表的活跃元组数、死元组数、上次手动 vacuum 时间、上次自动 vacuum 时间。
我常用的判断标准是:死元组数超过活跃元组数的 20%,或者某张表的 last_autovacuum 时间距离现在非常远,就要提高警惕。前者说明 autovacuum 可能来不及跑,后者说明 autovacuum 被禁用或频繁失败。
这里有个常见误区:很多人看到死元组高就立刻手动 VACUUM FULL,这是危险的。VACUUM FULL 会拿 ACCESS EXCLUSIVE 锁,业务会直接停顿。普通 vacuum(不带 FULL)是可以在线做的,如果 autovacuum 没跑,先手动执行普通 VACUUM 观察效果,再检查是不是 autovacuum 参数配置问题,比如 autovacuum_max_workers 太少、autovacuum_vacuum_cost_limit 太小。
3.5 复制与 WAL:主从延迟、归档状态
如果你的 PostgreSQL 是用在主从复制架构里,pgmetrics 对复制状态的展示非常有用。它会列出每个 standby 节点的接收延迟、回放延迟,以及 WAL 归档目录中尚未归档的文件数量。
复制延迟变大,不只是监控数字难看,还意味着主库宕机后你可能丢失更多数据,或者备库查询结果严重滞后。pgmetrics 里如果看到回放延迟,我会先确认备库磁盘 IO 是不是瓶颈,因为备库回放 WAL 是顺序写,磁盘慢会直接表现为延迟涨。
WAL 归档状态更隐蔽。pg_stat_archiver 里的 archived_count 如果长时间不增长,或者 failed_count 在增加,说明归档命令出了问题。这种事情不会主动告警,但一旦主库需要做时间点恢复,你会发现归档缺失,后果很严重。
3.6 关键配置快照:不用翻 postgresql.conf
pgmetrics 还会把数据库的当前配置参数输出一份,包括 max_connections、shared_buffers、work_mem、maintenance_work_mem、wal_level、checkpoint_timeout、max_wal_size 等。这个功能看起来简单,实际排查时特别有用:我不用登录服务器去翻 postgresql.conf,也不用执行 SHOW ALL 去大海捞针。
有些参数是 postgresql.conf 里改了但没 reload 的,pgmetrics 输出的是“当前生效值”。如果发现实际运行参数和配置文件不一致,说明可能有人改了配置没重载,或者用了 ALTER SYSTEM 但没重启实例。这种地方很容易埋隐患。
4. 无网页也能看懂数据库健康:文本模式的排查实战
4.1 场景:一个连接数爆满的深夜
有一次线上库报错越来越频繁,应用日志里出现了大量 FATAL: sorry, too many clients already。我登录服务器后没有急着去翻监控平台,先用 pgmetrics 跑了一次:
bash复制pgmetrics -u postgres --dbname postgres --no-color
文本模式输出里,连接部分直接显示了当前连接数和最大连接数,使用率已经到了 98%。数据库列表里还能看到哪个库占用的连接最多。到这里,“连接不够用”这个方向已经确认了。
4.2 从指标里看到更深处的问题
接着往下翻,我注意到 idle in transaction 的数量非常多,大约占了总连接数的三成。这些连接表面上看是在事务里,但实际上已经停顿了很长时间。应用侧连接池里可能有语句开了事务后没有提交,业务代码也没有设置超时。
再往下看,表统计部分的一张核心业务表死元组比例已经超过 25%,last_autovacuum 时间停留在好几个小时之前。原因很清晰:大量 idle in transaction 连接持有锁,autovacuum 尝试清理这张表时拿不到足够的锁,反复重试后放弃,于是死元组越积越多。死元组多了,表的扫描代价变大,查询变慢,应用层的连接占用时间更长,进一步加剧连接数紧张。
这一连串因果,如果只看 Grafana 的 CPU 和连接数曲线,最多只能看到“连接打满了,CPU 升高了”,很难一眼定位到真正的问题是应用层事务没提交。pgmetrics 一次文本输出把这些信息放在同一屏,我不用切十几个视图就能串起因果关系。
4.3 从指标到动作
当天晚上我做了三件事:
第一,临时调大 max_connections 并重启应用连接池,把业务先恢复。这里注意,调大 max_connections 是有代价的,每个连接都会消耗内存,不能无脑调,只是应急。
第二,在数据库侧配置了 idle_in_transaction_session_timeout = 30s,让超过 30 秒还停留在事务里的会话自动断开。这个参数在 PostgreSQL 9.6 以上版本都支持,对绝大多数应用是安全的,但需要业务确认没有跨事务的长连接场景。
第三,手动执行了一次普通 VACUUM 核心表,清掉死元组,让后续 autovacuum 能恢复正常节奏。
跑完这些之后,再用 pgmetrics 复查:连接使用率降到了 40%,idle in transaction 清零,死元组比例回到 5% 以下。这个案例让我对 pgmetrics 文本模式的印象很深刻,它不花哨,但在“我需要现在就搞清楚发生了什么”的场景里,非常顺手。
5. JSON 模式接入监控体系:从告警到巡检脚本
5.1 JSON 输出到底长什么样
pgmetrics 的 --json 输出是一个大 JSON 文档,顶层包含几个主要分组。正因为是结构化数据,它可以被 jq、Python、Go 等任意工具消费。我通常先跑一遍看看结构:
bash复制pgmetrics -u postgres --dbname postgres --json | jq 'keys'
大概会看到 database、connections、bgwriter、replication、system 等字段。字段名很直观,基本能从命名猜到含义。举个例子,数据库汇总部分通常长这样:
json复制{
"name": "appdb",
"size_bytes": 42949672960,
"xact_commit": 100000,
"xact_rollback": 500,
"blks_read": 1000000,
"blks_hit": 99000000,
"temp_bytes": 0,
"deadlocks": 3
}
注意 size_bytes 是字节,想要显示成 GB 需要除以 1024 的三次方,这个问题我一开始没注意,脚本里直接打印字节数,看得一脸懵。
5.2 用 jq 提取关键字段
日常脚本里我常用几个 jq 表达式:
bash复制# 所有数据库的缓存命中率
pgmetrics -u postgres --dbname postgres --json \
| jq '.databases[] | {name, cache_hit_ratio: ((.blks_hit / (.blks_hit + .blks_read)) * 100)}'
# 当前连接使用率
pgmetrics -u postgres --dbname postgres --json \
| jq '.connections | {current, max, usage_percent: ((.current / .max) * 100)}'
# 复制延迟
pgmetrics -u postgres --dbname postgres --json \
| jq '.replication[]? | {client_addr, replay_lag_bytes}'
这里有个小坑:当实例不是主库,或者没有配置复制时,replication 数组可能不存在。jq 表达式里加一个 ? 可以避免报错,我经常因为这个细节在脚本里翻车。
5.3 巡检脚本设计
受够了手工巡检之后,我写过一个简单的每日巡检脚本:凌晨跑一次 pgmetrics,把 JSON 存成带日期的文件,然后用 jq 提取关键字段,拼成表格格式的报告。核心逻辑大概是:
bash复制#!/usr/bin/env bash
set -euo pipefail
export PGMETRICS_PASSWORD="$DB_PASSWORD"
OUT="/var/lib/pgmetrics-reports/pgmetrics-$(date +%F).json"
LOG="/var/lib/pgmetrics-reports/health-$(date +%F).txt"
pgmetrics -u "$DB_USER" --dbname "$DB_NAME" --host "$DB_HOST" --json > "$OUT"
jq -r '
"数据库巡检报告 " + .system.timestamp,
"----------------------------------------",
"连接使用率: " + ((.connections.current / .connections.max) * 100 | tostring) + "%",
"最大连接数: " + (.connections.max | tostring),
"当前连接数: " + (.connections.current | tostring)
' "$OUT" > "$LOG"
再配合一段循环,把所有数据库的缓存命中率、死锁次数、死元组比例超过阈值的表打印出来。整个脚本不到 100 行,却能覆盖大部分日常巡检需求,输出文件还能直接归档留痕,比每次手工 psql 快多了。
对于已有 Prometheus 的团队,JSON 模式的另一个价值是可以做一次性数据导入,比如把 pgmetrics 采集的结果转成 Prometheus remote write 格式,或者简单地在告警 webhook 里附带一份 JSON 上下文。虽然它不会主动暴露 /metrics 端口给 Prometheus 抓取,但你完全可以把它当做一个“按需采集器”,在需要时才拉取数据。
5.4 简单告警推送
告警逻辑也可以写得很朴素:脚本里对关键指标做阈值判断,一旦异常就调用钉钉或企业微信的 webhook,把结果发到群里。比如:
bash复制CONN_USAGE=$(jq -r '.connections.current / .connections.max * 100' "$OUT")
if (( $(echo "$CONN_USAGE > 90" | bc -l) )); then
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"连接使用率告警: ${CONN_USAGE}%\"}}"
fi
这种方案的好处是不用引入额外组件,一台机器、一个 cron、几行 bash 就能跑起来。坏处是没有历史趋势图,无法做长期基线分析。所以我的建议是:轻量场景、临时场景用 pgmetrics 脚本巡检;如果业务规模到了需要趋势判断和复杂告警规则的阶段,再上 Prometheus 那套体系也不迟。
6. 权限配置与常见报错:我在生产环境踩过的坑
6.1 最小权限:pg_monitor 角色怎么用
pgmetrics 要读的视图非常多,包括 pg_stat_database、pg_stat_all_tables、pg_stat_bgwriter、pg_settings 等。一开始我图省事,直接用 superuser 跑,后来觉得总是拿最高权限不好,尤其放在 cron 脚本里,一旦服务器被入侵,等于把数据库全部权限交给了对方。
PostgreSQL 10 开始提供了内置角色 pg_monitor,它包含 pg_read_all_settings、pg_read_all_stats 和 pg_stat_scan_tables 这三项权限,刚好覆盖监控所需的数据读取能力。推荐的做法是创建一个专用账号:
sql复制CREATE USER pgmetrics_user WITH PASSWORD '复杂度足够的密码';
GRANT pg_monitor TO pgmetrics_user;
然后用这个账号跑 pgmetrics。实测下来,pg_monitor 角色可以读取大部分指标,包括连接、数据库统计、表统计、复制状态。如果你还在使用 PostgreSQL 9.6 或更早版本,没有 pg_monitor 角色,这时候要么使用普通用户并手动授予部分视图的 SELECT 权限,要么只能选择 superuser。我建议尽量升级版本,9.6 已经停止维护很久了,安全风险不只是监控权限这一项。
权限不足时的典型报错是 permission denied for relation pg_authid 或者 permission denied for view pg_stat_activity。看到这类报错第一反应不是去开 superuser,而是先确认账号是否已经有 pg_monitor 角色。缺什么补什么,保持最小权限原则。
6.2 连接失败和密码认证问题
另一个高频报错是连接失败:
code复制connection to server at "127.0.0.1", port 5432 failed: FATAL: password authentication failed for user "xxx"
这个大概率不是 pgmetrics 的问题,而是连接串或密码配置有误。排查步骤很简单:第一条,确认环境变量 PGMETRICS_PASSWORD 是否被错误设置成了旧密码,环境变量的优先级很高,很容易让你以为自己在用新密码,实际跑的还是旧的;第二条,确认 .pgpass 文件格式和权限,如果文件权限不是 600,libpq 会直接忽略它,而且不会给你报错提示,只表现为“每次都要交互输密码”或“认证失败”;第三条,确认 pg_hba.conf 里对应来源的认证方式是 md5/scram,而不是 trust,否则等于裸奔。
还有一次我遇到一个容易忽略的问题:目标实例的 max_connections 已经打满,导致 pgmetrics 本身都无法建立新连接。这就很尴尬了,排查连接问题的工具连不上数据库。遇到这种情况,我一般先通过现有连接(比如已经连着的 psql)用超级用户杀掉部分空闲连接,或者从连接池里临时释放一批,给 pgmetrics 留一个连接位。这也是为什么推荐保留一个 superuser 的备用登录途径。
6.3 不要在生产环境高频轮询
pgmetrics 本身设计为按需采集,每次运行都会创建连接并查询一批系统视图。它不会像 agent 一样一直驻留,所以对数据库的额外负担较小。但我见过有同事把它挂在 cron 里每 10 秒跑一次,这就有点过了。系统视图的查询本身轻量,但高频连接的建立和释放、频繁读取统计视图,在高并发生产环境里依然会产生不必要的开销。
我个人的实践频率是:常规巡检一天一次,遇到问题排查时手动多次运行,如果确实需要秒级监控,那应该上 Prometheus 或者商业监控工具,而不是靠 cron 高频跑 pgmetrics。工具各有适用场景,硬要用螺丝刀去拧螺丝钉是可以,但效率不会高。
最后再分享一个小技巧:如果你经常要跑 pgmetrics,可以把它默认连接信息写进 .pg_service.conf 或者直接封装一个 shell 别名,比如:
bash复制alias pgcheck='pgmetrics -u postgres --dbname postgres --host 127.0.0.1'
这样每次排查只需要敲 pgcheck 加一个 --json 或 --no-color 就行。工具本身不复杂,但把它变成自己的肌肉记忆之后,排查效率真的能提升一个档次。
