PostgreSQL 的 I/O 一直是运维和调优中最难看清的盲区——你知道磁盘忙,但不知道到底是哪种操作在忙、忙在哪里、花了多少时间。pg_stat_io 正是为了填补这个缺口而引入的视图。Robert Haas 近期宣布,2026 年 6/7 月的 PostgreSQL Hacking Workshop 将邀请 Melanie Plageman 一起深入讨论她在 pgconf.dev 上的演讲《Additional IO Observability in Postgres with pg_stat_io》。如果你在生产环境调优中曾被 I/O 问题困扰,这次 workshop 和这个视图都值得重点关注。
pg_stat_io 能看到什么
传统上我们用 pg_stat_database 里的 blks_read / blks_hit,或者操作系统层面的 iostat,来推断 I/O 状况。但这些指标要么粒度太粗(只到数据库级别),要么脱离了 PostgreSQL 内部语义(不知道是 checkpoint 写、后台写还是扩展表写)。
pg_stat_io 按三个维度拆分 I/O 统计:
- 对象类型(object_type):relation、temp relation、shared buffer 等
- 上下文(context):normal、vacuum、checkpoint、bgwriter、extend 等
- 操作(op):read、write、extend、evict、reuse、fsync
每一行记录给出 reads、writes、extends、evictions、reuses、fsyncs 的次数,以及 read_bytes、write_bytes、extend_bytes 等字节量,还有 evict_time、read_time、write_time、extend_time 等耗时(微秒)。
这意味着你可以回答诸如:
- vacuum 期间读了多少字节、花了多少时间?
- checkpoint 写和 bgwriter 写的比例如何?
- 表扩展(extend)操作是否频繁到值得关注?
- 共享缓冲区中被 evict 的块有多少、耗时多长?
实战:用 pg_stat_io 定位 I/O 瓶颈
下面是一个可以直接在 PostgreSQL 15+ 上运行的查询,帮你快速找出最耗时的 I/O 上下文和操作组合:
-- 查看各上下文+操作的 I/O 耗时与吞吐,按耗时降序
SELECT
object_type,
context,
op,
reads,
writes,
extends,
evictions,
reuses,
fsyncs,
read_bytes,
write_bytes,
extend_bytes,
-- 耗时单位为微秒,转为毫秒更直观
round(read_time / 1000.0, 2) AS read_time_ms,
round(write_time / 1000.0, 2) AS write_time_ms,
round(extend_time / 1000.0, 2) AS extend_time_ms,
round(evict_time / 1000.0, 2) AS evict_time_ms
FROM pg_stat_io
WHERE reads > 0 OR writes > 0 OR extends > 0 OR evictions > 0
ORDER BY (read_time + write_time + extend_time + evict_time) DESC;
运行后你会看到类似这样的输出(截取关键几行):
object_type | context | op | reads | writes | ... | read_time_ms | write_time_ms | ...
-------------+-------------+-------+-------+--------+-----+--------------+---------------+-----
relation | normal | read | 12480 | 0 | ... | 38.50 | 0.00 | ...
relation | checkpoint | write | 0 | 3200 | ... | 0.00 | 42.10 | ...
relation | vacuum | read | 1800 | 0 | ... | 5.20 | 0.00 | ...
从这个结果可以快速判断:如果 checkpoint 的 write_time_ms 占大头,说明 checkpoint 写磁盘是瓶颈,可以考虑调大 checkpoint_completion_target 或减少 shared_buffers 里的脏页积压速度;如果 vacuum 的 read 耗时突出,说明 autovacuum 在大量扫描表,可能需要调整 autovacuum_vacuum_cost_limit 或给热点表设置自定义参数。
监控脏页回写节奏
另一个常见问题是:脏页到底是被 checkpoint 写出去的,还是被 bgwriter 提前刷掉的?比例失衡往往意味着 checkpoint 集中写导致 I/O 尖峰。下面的查询直接对比两者:
SELECT
context,
writes,
write_bytes,
round(write_time / 1000.0, 2) AS write_time_ms
FROM pg_stat_io
WHERE context IN ('checkpoint', 'bgwriter')
AND op = 'write';
理想状态下 bgwriter 应该承担更多写入,让 checkpoint 在目标时间内均匀完成。如果 checkpoint 的 write_bytes 远大于 bgwriter,可以尝试调高 bgwriter_lru_maxpages 或降低 bgwriter_delay,让后台写更积极。
持续采集:把 pg_stat_io 接入你的监控栈
单次查询只能看累计值。要观察趋势,需要定期快照并计算增量。以下是一个用 psql + cron 的最小方案:
# 每分钟采集一次,追加到 CSV 文件
# 部署到监控机器上,用 cron 调度:* * * * * /path/to/collect_pg_stat_io.sh
COLLECT_SQL="
COPY (
SELECT
now() AS ts,
object_type, context, op,
reads, writes, extends, evictions, reuses, fsyncs,
read_bytes, write_bytes, extend_bytes,
read_time, write_time, extend_time, evict_time
FROM pg_stat_io
) TO STDOUT WITH CSV HEADER
"
psql -h $PGHOST -U $PGUSER -d $PGDB -c "$COLLECT_SQL" \
>> /var/log/pg_stat_io_metrics.csv
有了 CSV 之后,用 Grafana / Prometheus 或者简单的 Python 脚本计算每分钟增量,就能画出 I/O 吞吐和耗时的时序曲线。如果你已经在用 pg_stat_statements 做查询级别监控,把 pg_stat_io 加进来可以补上"查询慢是因为磁盘慢还是因为计算慢"这块拼图。
上手建议
- 版本要求:
pg_stat_io从 PostgreSQL 16 开始可用(早期版本为实验性视图,字段可能有差异)。升级前先在测试环境确认视图结构。 - 权限:查询
pg_stat_io需要pg_read_all_stats预定义角色或超级用户。给监控账号授权时用GRANT pg_read_all_stats TO monitoring_user;,不要直接给 superuser。 - 冷启动注意:视图统计是累计值,重启后清零。首次采集时先记录基线,后续用差值分析。
- 与现有指标互补:不要用
pg_stat_io替代pg_stat_database,两者粒度不同。前者看 I/O 语义,后者看逻辑读写比例,结合使用效果最好。
如果你想和 Melanie Plageman 及社区开发者一起深入讨论 pg_stat_io 的细节和未来方向,Robert Haas 的 Hacking Workshop 正在招募参与者——通过报名表单申请即可收到会议邀请。对于正在做 PostgreSQL I/O 调优或监控建设的人来说,这是一个直接和视图设计者对话的好机会。