PostgreSQL 15物化视图无自动刷新机制,需用语句级触发器或定时任务;FOR EACH ROW触发器禁止REFRESH,须用FOR EACH STATEMENT;CONCURRENTLY刷新需满足三前提:非空、唯一索引、显式CONCURRENTLY;高频写入宜定时刷新;触发器须覆盖INSERT/UPDATE/DELETE/TRUNCATE。

PostgreSQL 15 里没有真正的“自动刷新”机制——MATERIALIZED VIEW 仍是静态快照,必须靠外部动作触发 REFRESH MATERIALIZED VIEW。所谓“自动”,实际是两种可落地的工程方案:语句级触发器监听变更,或定时任务调度刷新。选哪种,取决于你对数据新鲜度、写入频率和系统稳定性的权衡。
为什么不能用 FOR EACH ROW 触发器刷新物化视图
这是最常踩的坑:函数能创建成功,但第一次 INSERT/UPDATE 就报错退出。
-
REFRESH MATERIALIZED VIEW是语句级操作,PostgreSQL 明确禁止在FOR EACH ROW触发器中执行,会直接抛出ERROR: REFRESH MATERIALIZED VIEW cannot be executed from a trigger - 即使绕过语法检查(比如用
dblink异步调用),一次批量插入 N 行也会触发 N 次刷新,而物化视图内容只变一次,纯属资源浪费 - 正确做法是定义为
FOR EACH STATEMENT,确保每个INSERT/UPDATE/DELETE语句只触发一次刷新
CONCURRENTLY 刷新的三个硬性前提缺一不可
想让刷新不锁表、允许并发读,必须同时满足:
- 物化视图已存在且非空(首次初始化必须用普通
REFRESH,不能加CONCURRENTLY) - 物化视图上建有至少一个
UNIQUE索引(例如CREATE UNIQUE INDEX ON mv_daily_sales (day)) - 刷新语句里显式写
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_name,漏掉CONCURRENTLY就会拿ACCESS EXCLUSIVE锁
常见错误是建完物化视图忘了加唯一索引,结果刷新时卡住或报 ERROR: cannot refresh materialized view "mv_xxx" concurrently, because it has no unique index。
高频写入场景下,触发器反而成瓶颈
当基表每秒 DML 超过 10–20 次(如埋点日志、实时订单流水),每次变更都刷一次物化视图,I/O 和锁竞争会迅速拖垮性能。
- 此时应放弃触发器,改用定时刷新:例如用
pg_cron每 5 分钟跑一次REFRESH MATERIALIZED VIEW CONCURRENTLY - 若连
pg_cron都不可用,可用系统crontab+psql -c "REFRESH ...",或开发环境用\watch 300(每 5 分钟刷新) - 注意:所有“定时”方案本质仍是全量重算,不是增量;真正降低开销得靠业务层加时间戳/ID 位点 + 手动 upsert,但需源表有单调递增字段
TRUNCATE 和 UPDATE 同样会让物化视图失效
只监听 INSERT 是典型疏漏。实际中,UPDATE 改了聚合字段、DELETE 删了记录、TRUNCATE 清空整表,都会导致物化视图数据陈旧。
- 触发器定义必须覆盖全部事件:
AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON base_table - 不要拆成多个触发器(如一个管 INSERT,一个管 DELETE),否则维护困难且易漏事件
- 如果基表支持
TRUNCATE(PostgreSQL 12+),漏掉它会导致清空后物化视图完全失同步,且无任何错误提示
真正难处理的是“部分更新”场景:比如物化视图按天聚合,但某条记录的 created_at 被 UPDATE 修改到前一天——这种逻辑一致性无法靠触发器自动捕获,必须靠应用层约束或额外校验逻辑兜底。

















