concurrently 刷新不阻塞读但默认更慢,因需全表扫描、双索引比对及行级锁;须有非空唯一索引、首次普通刷新后才可用,仅在读瓶颈且数据变化率低时提升读吞吐。

REFRESH MATERIALIZED VIEW CONCURRENTLY 能避免读阻塞,但不是“开箱即用”的吞吐提升方案——它默认比普通刷新更慢、更耗资源,只有在满足硬性前提且业务能接受最终一致性时,才值得启用。
为什么加了 CONCURRENTLY 反而更慢?
并发刷新不重写整张表,而是走「差集比对」路径:先执行源查询生成临时快照,再用唯一索引逐行比对旧数据与快照,最后只 INSERT/UPDATE/DELETE 差异行。这意味着:
- 必须全量扫描源表(哪怕只改了一行)
- 要两次索引扫描(旧视图索引 + 快照索引)
- 每行变更都触发 WAL 写入和行级锁竞争
- 若底层表在刷新期间被高频写入,可能反复重试比对,拖慢整体事务
CONCURRENTLY 刷新失败的三个典型报错及修复
只要没建对唯一索引,REFRESH MATERIALIZED VIEW CONCURRENTLY 就会直接拒绝执行,不会进入刷新流程。
-
ERROR: cannot refresh materialized view "mv_orders" concurrently—— 物化视图上没有UNIQUE或PRIMARY KEY索引;建索引必须显式执行CREATE UNIQUE INDEX ON mv_orders (id),源表主键不会自动同步到 MV -
ERROR: unique index on materialized view must not contain nullable columns—— 索引列含NULL值;可用CREATE UNIQUE INDEX ON mv_orders (COALESCE(id, 0))包裹,或加WHERE id IS NOT NULL -
could not lock updated tuple in materialized view—— 刷新末尾尝试更新某行时,该行已被业务UPDATE修改;这不是死锁,是并发写冲突,需重试或调整业务写入逻辑
哪些场景下 CONCURRENTLY 才真能提吞吐?
它提升的是「读请求吞吐」,不是「刷新吞吐」。关键看你的瓶颈是否在查询排队上:
- 报表页面大量
SELECT并发访问同一物化视图,且无法容忍秒级卡顿 → 启用CONCURRENTLY后,读请求照常响应,吞吐稳定 - 物化视图数据变化率低(比如日汇总,每天只增千行),差异小 → 差集计算快,刷新耗时不显著拖累后台任务
- 你已预留足够磁盘空间(临时快照需额外 ~1.5 倍原视图大小),且监控到
pg_stat_progress_refresh_matview中的phase长期卡在scanning或comparing→ 说明当前配置可行 - 不能在事务块或函数里调用:
REFRESH MATERIALIZED VIEW CONCURRENTLY不支持嵌套在BEGIN...END中,否则报cannot be executed from a function or multi-statement query
真正容易被忽略的是:即使索引建对、字段非空、列全覆盖,如果物化视图刚用 WITH NO DATA 创建,还没执行过首次填充,CONCURRENTLY 也会直接报错。必须先用普通刷新填一次数据,后续才能并发刷新。










