CONCURRENTLY刷新直接失败是因为PostgreSQL在解析阶段就强制校验唯一索引:必须显式创建在物化视图上、覆盖全部分组列、所有列NOT NULL、状态VALID且视图已非空,缺一则报“does not have a unique index”并拒绝执行。

必须用 REFRESH MATERIALIZED VIEW CONCURRENTLY,但前提是物化视图已建好满足全部条件的唯一索引,否则命令直接报错、根本不执行。
为什么 CONCURRENTLY 刷新会直接失败?
不是慢、不是卡,是 PostgreSQL 在解析阶段就拒绝执行。错误信息固定为:ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index。
- 索引必须显式创建在物化视图上(
CREATE UNIQUE INDEX ON mv_name (id)),基表主键或约束不会自动继承 - 索引列全部需为
NOT NULL;若源字段允许NULL,得用COALESCE(col, 0)包裹后建索引 - 聚合视图中含
GROUP BY a, b,索引必须覆盖全部分组列(如(a, b)),只建(a)会导致比对逻辑错乱 - 索引状态必须为
VALID;刚建完可能处于INVALID,需等一次VACUUM或ANALYZE后才生效 - 物化视图不能是空壳(
WITH NO DATA状态)——必须先用普通刷新填过一次数据
REFRESH MATERIALIZED VIEW CONCURRENTLY 实际不锁表,但会干扰什么?
它不阻塞 SELECT,但不是“零代价”操作,真实干扰集中在写冲突和资源竞争上:
- 仍持有
SHARE UPDATE EXCLUSIVE锁:DROP、TRUNCATE、ALTER物化视图的操作会被阻塞 - 刷新过程中业务写入若与新数据产生相同唯一键,直接报错:
ERROR: duplicate key value violates unique constraint - 最后 merge 阶段若某行刚被业务
UPDATE过,会失败并提示:could not lock updated tuple in materialized view - 每次刷新多占一倍磁盘空间存临时副本;高频刷新(如每分钟)易撑爆
pg_wal或base目录 - 性能通常比普通刷新慢 2–3 倍:要全量跑原始查询 + 差集计算 + 两次索引扫描
触发器自动刷新时最常踩的坑
想靠触发器实现“自动刷新”,必须避开几个硬性限制:
- 只能用
FOR EACH STATEMENT,禁用FOR EACH ROW;后者会报错:REFRESH MATERIALIZED VIEW cannot be executed from a trigger - 触发事件必须覆盖
INSERT OR UPDATE OR DELETE,漏掉UPDATE或DELETE就会导致数据过期 - 如果基表支持
TRUNCATE(PostgreSQL 12+),建议加上OR TRUNCATE,否则清空后物化视图不同步 - 函数里必须显式写
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_name;漏掉CONCURRENTLY就退化成锁表刷新 - 高频写入场景下,触发器本身会成为瓶颈——一次事务内多行变更只应触发一次刷新,但误配仍可能放大开销
真正容易被忽略的是:即使索引建对了、字段非空、列全覆盖,只要物化视图还没被普通刷新填充过数据,CONCURRENTLY 就永远执行不了——它不处理“从无到有”,只做“新旧比对”。

















