REFRESH MATERIALIZED VIEW 不必强制带 CONCURRENTLY,但生产环境几乎必须加,否则会阻塞所有 SELECT;加 CONCURRENTLY 需满足三条件:物化视图非空、有显式 UNIQUE 索引、语句中明确写出 CONCURRENTLY 关键字。

REFRESH MATERIALIZED VIEW 是唯一安全、标准的更新方式;物化视图不是普通表,不能用 INSERT/UPDATE/DELETE 直接修改其内容。
REFRESH MATERIALIZED VIEW 必须带 CONCURRENTLY 吗?
不一定,但生产环境几乎必须加,否则会锁死所有读请求:
- 不加
CONCURRENTLY:执行期间整个物化视图不可读(SELECT阻塞),适合离线维护场景 - 加
CONCURRENTLY:允许并发读,但有硬性前提,缺一不可:- 物化视图已存在且非空(首次初始化必须用不带
CONCURRENTLY的刷新) - 物化视图上至少有一个
UNIQUE索引(例如CREATE UNIQUE INDEX idx_mv_id ON mv_summary(id)) - 刷新语句里显式写出
CONCURRENTLY—— 写成REFRESH MATERIALIZED VIEW mv_summary就会退化为全表锁
- 物化视图已存在且非空(首次初始化必须用不带
常见报错 cannot refresh materialized view "mv_xxx" concurrently, because it has no unique index 就是漏了第二条。
触发器自动刷新时,为什么 FOR EACH ROW 一定失败?
因为 PostgreSQL 内核在设计层面禁止在行级触发器中调用 REFRESH MATERIALIZED VIEW:
- 函数定义能通过,但第一次触发就报错:
ERROR: REFRESH MATERIALIZED VIEW cannot be executed from a trigger - 原因不是权限或语法,是事务模型冲突:REFRESH 是重量级语句级快照操作,无法适配单行变更粒度
- 批量插入(如
INSERT INTO t VALUES (),(),())会触发 N 次刷新,纯属重复 I/O 和锁竞争
正确写法只能是:
FOR EACH STATEMENT- 事件覆盖完整:
AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON base_table - 刷新函数内调用
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_name
高频写入表上建触发器刷新,为什么反而更慢?
当基表每秒 DML 超过 20–30 次(比如日志表、埋点表),触发器方案会成为瓶颈:
- 每次 DML 都触发一次
REFRESH MATERIALIZED VIEW CONCURRENTLY,而该操作本身要扫描全量基表 + 比对索引 + 生成临时表 - 并发刷新虽不锁读,但会产生大量临时 I/O 和 WAL 压力,可能拖垮整体吞吐
- 更隐蔽的问题:多个并发刷新可能互相等待索引锁,导致“假死”
这时应放弃触发器,改用:
- 定时任务(如
pg_cron每 5 分钟刷一次) - 或增量合并模式:用触发器写变更日志表,再由后台作业批量
INSERT ... ON CONFLICT DO UPDATE合并到物化视图
真正容易被忽略的是:物化视图的“更新”本质是重建,不是修改。哪怕只改基表一行,REFRESH 仍需重跑整个 AS 子查询。它没有“局部更新”能力,所有所谓“实时”都是权衡出来的妥协。

















