postgresql中真正反映“谁被改得最多”的是pg_stat_all_tables视图的n_tup_upd和n_tup_del字段,需结合时间维度、过滤系统表、排除autovacuum干扰,并对比n_tup_hot_upd占比以识别真实业务更新压力。

查 pg_stat_all_tables 看更新最猛的表
PostgreSQL 里真正反映“谁被改得最多”的,是 pg_stat_all_tables 视图里的 n_tup_upd 和 n_tup_del 字段——不是看日志,也不是靠猜。这个视图每行对应一张表,数值累计自上次统计重置(或集群启动),所以必须结合时间维度判断“高频”是否真实发生。
-
n_tup_upd是 UPDATE 触发的行级更新次数(哪怕只改一个字段也算一次) -
n_tup_del是 DELETE 次数,和 UPDATE 常成对出现(比如软删逻辑) - 别只盯着绝对值大的表——先用
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')过滤系统表 - 如果刚重启过数据库,这些计数器是空的,
pg_stat_all_tables就没参考价值
SELECT schemaname, relname, n_tup_upd + n_tup_del AS total_dml
FROM pg_stat_all_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY total_dml DESC
LIMIT 5;
用 pg_stat_statements 定位具体哪条 SQL 在狂改
pg_stat_statements 才是定位“谁在改、怎么改”的关键。它记录执行过的 SQL 文本及其调用次数、总耗时、扫描行数等——但默认不启用,且需要 superuser 权限开启。
- 确认已启用:
shared_preload_libraries = 'pg_stat_statements'在postgresql.conf中,且重启生效 - 执行
CREATE EXTENSION pg_stat_statements;(仅需一次) -
calls高 +rows低,大概率是单行 UPDATE/DELETE;rows高 +calls低,可能是批量操作 - 注意:该插件会归一化 SQL(比如把
WHERE id = 123变成WHERE id = $1),所以不能直接匹配业务代码中的字面量
SELECT query, calls, rows, shared_blks_hit FROM pg_stat_statements WHERE query ILIKE '%UPDATE%' OR query ILIKE '%DELETE%' ORDER BY calls DESC LIMIT 3;
警惕 autovacuum 和 HOT 更新干扰判断
高频修改的表,常伴随大量 autovacuum 活动,而 pg_stat_all_tables.n_tup_upd 会把 HOT 更新(Heap-Only Tuples)也计入——这会让“业务修改量”被高估。
- HOT 更新不产生新 tuple 版本,也不触发 vacuum,但它仍算一次
n_tup_upd - 如果某张表
n_tup_upd很高,但n_tup_hot_upd占比超 70%,说明大部分更新是轻量级的、无锁的,实际压力未必大 - 检查方法:
SELECT relname, n_tup_upd, n_tup_hot_upd FROM pg_stat_all_tables WHERE ... - autovacuum 自身的 UPDATE(比如清理 toast 表)也会出现在
pg_stat_statements中,用户名是autovacuum,可过滤掉
监控窗口要够长,但别等太久才看
修改频率有明显周期性(比如每小时跑一次批处理,或凌晨刷新报表缓存),只看“当前一分钟”的统计容易误判。
- 建议至少采集 15 分钟以上的
pg_stat_all_tables差值(用两次SELECT结果相减) - 如果应用刚上线或配置刚改,前几小时数据波动大,别急着下结论
- 不要依赖单次查询结果做扩容或分库决策——连续观察 2–3 个业务周期更可靠
- 某些 ORM(如 Django 的
save())会无差别 UPDATE 全字段,即使只改了一个字段,也会让n_tup_upd虚高
n_tup_upd 排序,很容易把“看起来很忙”的表当成瓶颈。










