必须先为物化视图创建满足条件的唯一索引(所有列NOT NULL、覆盖全部分组列、无函数/表达式),再执行普通刷新填充数据,之后才能使用CONCURRENTLY刷新;它不锁表但非增量,性能通常更差。

CONCURRENTLY 刷新报错 “does not have a unique index” 怎么办
直接执行 REFRESH MATERIALIZED VIEW CONCURRENTLY 一定会失败,除非你已在物化视图上建好满足全部硬性条件的唯一索引。错误信息固定为:ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index。这不是权限或配置问题,是命令入口级校验——没索引,根本不会开始执行。
- 索引必须显式建在物化视图本身上:
CREATE UNIQUE INDEX ON my_mv (tenant_id, event_type);源表有主键、约束带唯一性,都不算数 - 所有索引列必须
NOT NULL;若字段允许 NULL,得先用COALESCE(col, 'placeholder')包裹再建索引 - 聚合类视图(含
GROUP BY a, b)必须把全部分组列放进索引,且顺序一致;只建(a)或颠倒顺序会逻辑错乱 - 刚建完索引可能状态是
INVALID,需执行一次VACUUM my_mv或ANALYZE my_mv才被识别 - 表达式索引(如
(UPPER(name)))、带WHERE条件的索引、函数索引,一律不被接受
物化视图刚创建就跑 CONCURRENTLY 为什么还是失败
即使索引全对、字段非空、列全覆盖,REFRESH MATERIALIZED VIEW CONCURRENTLY 仍会拒绝执行,如果该物化视图处于 WITH NO DATA 状态(即空壳)。它要求视图中已存在原始数据,否则无法进行新旧行比对。
- 必须先用普通刷新填充一次:
REFRESH MATERIALIZED VIEW my_mv - 之后才能安全执行:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv - 首次刷新不能加
CONCURRENTLY,这是硬性流程顺序,不是可选项
CONCURRENTLY 刷新期间写入冲突的真实表现
它不锁表,但不是“无感”。实际运行中,业务 DML 与刷新过程在唯一键和行锁层面直接交锋,失败不是静默跳过,而是明确报错并中断。
- 新数据要插入
order_id = 123,但业务刚好执行了INSERT INTO ... VALUES (123, ...)→ 报错:ERROR: duplicate key value violates unique constraint - 某行在差集计算后、最终替换前被业务
UPDATE过 → 报错:could not lock updated tuple in materialized view - 失败后状态不可靠:可能已插入部分新行、未删除对应旧行,导致重复或缺失;必须手动清理或重建,不能简单重试
为什么并发刷新反而更慢、还更耗资源
CONCURRENTLY 的目标从来不是提速,而是避免读阻塞。它的底层仍是全量执行原始查询 + 全量扫描源表 + 两次索引扫描(新旧数据各一次)+ 差集计算(EXCEPT/JOIN),实测 400 万行下比普通刷新慢 2–4 倍。
- 每次刷新都需额外磁盘空间存临时副本;高频刷新(如每分钟)易触发空间告警
- 不能在事务块里使用
CONCURRENTLY版本,否则直接报错 - 定义中含
now()、random()、CURRENT_USER或未配ORDER BY的DISTINCT ON,会导致新旧结果无法精确比对,刷新失败
真正容易被忽略的是:它不是增量,只是“不锁表的全量”。如果你指望靠它减少 CPU 或 I/O,反而会失望;设计时就得接受它比普通刷新更重的事实。

















