必须先建唯一索引,否则 REFRESH MATERIALIZED VIEW CONCURRENTLY 直接报错;因 PostgreSQL 采用行级差集比对(JOIN/EXCEPT)实现增量更新,依赖唯一索引精准匹配新旧行,缺索引则无法识别对应关系而拒绝执行。

必须先建唯一索引,否则 REFRESH MATERIALIZED VIEW CONCURRENTLY 会直接报错,不是慢、不是卡,是根本执行不了。
为什么 CONCURRENTLY 刷新一定要求唯一索引
PostgreSQL 不是重写整张表,而是用新结果集和旧数据做行级比对:靠唯一索引列做 JOIN 或 EXCEPT 找出要插入、更新、删除的行。没有它,就无法判断“哪一行对应哪一行”,内核直接拒绝执行。
- 错误信息固定为:
ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index - 索引必须显式建在物化视图上:
CREATE UNIQUE INDEX ON mv_name (col),源表有主键不算数 - 所有索引列必须
NOT NULL;如果字段允许 NULL,得用COALESCE(col, 'placeholder')包裹后再建索引 - 聚合类物化视图(含
GROUP BY a, b)必须把全部分组列放进索引,只建(a)会导致逻辑错乱
哪些索引能被 CONCURRENTLY 识别
PostgreSQL 16 只认满足全部条件的索引,不接受“看起来像唯一”的变通写法:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- 类型必须是
UNIQUE或PRIMARY KEY(普通 B-tree 索引不行) - 状态必须为
VALID;刚建完可能显示INVALID,需等一次VACUUM或ANALYZE后才生效 - 定义必须在物化视图本身,不能建在源表或中间表上
- 不能含函数表达式(如
(UPPER(name)))、不能带WHERE条件(除非你确认刷新时能精确复现该条件) -
UNIQUE CONSTRAINT≠ 索引——某些迁移后或分区表场景下约束未触发隐式索引创建,必须手动CREATE UNIQUE INDEX
CONCURRENTLY 刷新失败后更难恢复
它不锁表,但失败行为比普通刷新更危险:
-
REFRESH MATERIALIZED VIEW失败时,物化视图内容不变,状态可预测;重试安全 -
REFRESH MATERIALIZED VIEW CONCURRENTLY失败时,可能已部分更新(比如插入了新行但没删完旧行),导致数据重复或缺失 - 常见失败原因包括:业务写入与新数据发生唯一键冲突(
duplicate key value violates unique constraint)、某行已被业务 UPDATE 过(could not lock updated tuple in materialized view) - 每次刷新都要额外磁盘空间存临时副本;高频刷新(如每分钟一次)容易触发空间告警
首次刷新必须用普通方式
CONCURRENTLY 不接受空壳物化视图,必须先初始化数据:
- 新建物化视图默认是
WITH NO DATA状态,此时直接跑CONCURRENTLY会报错 - 必须先执行一次:
REFRESH MATERIALIZED VIEW mv_name(不加CONCURRENTLY) - 再建好唯一索引,后续才能用
CONCURRENTLY刷新 - 别等到调度任务跑起来才发现失败——物化视图定义阶段就要让“主键语义”稳定、非空、可索引,比如优先选
tenant_id + event_id,而不是name这类易变字段

















