PostgreSQL 15 中 CREATE MATERIALIZED VIEW 不支持 CONCURRENTLY,因其本质是单次 DDL 操作,需独占锁且无并发语义基础;仅 REFRESH 支持 CONCURRENTLY,但要求物化视图已存在、有 VALID 唯一索引且非空。

PostgreSQL 15 中无法“并发构建”物化视图——CREATE MATERIALIZED VIEW 本身是单次、阻塞式操作,不支持 CONCURRENTLY 修饰;真正能并发执行的只有刷新动作:REFRESH MATERIALIZED VIEW CONCURRENTLY,但它要求物化视图已存在且满足严格前提。
为什么 CREATE MATERIALIZED VIEW 不支持 CONCURRENTLY
PostgreSQL 的物化视图创建过程本质是一次性 SELECT + INSERT INTO 新表,它不涉及“比对旧数据”,因此不存在并发安全问题的语义基础。官方语法明确排除了 CONCURRENTLY 选项——若强行加该关键字,会直接报错:ERROR: syntax error at or near "CONCURRENTLY"。
常见误解来源:把“并发刷新”(refresh)误记为“并发创建”(create)。两者在语义、实现机制和权限要求上完全隔离。
-
CREATE MATERIALIZED VIEW是 DDL,需独占锁,但只在创建瞬间持有(通常几秒到几十秒,取决于源查询耗时) -
REFRESH MATERIALIZED VIEW CONCURRENTLY是 DML 风格操作,依赖唯一索引做行级差集比对,全程不锁整个视图 - 试图用后台作业或多个连接同时执行
CREATE,只会触发序列化排队,不会提速,还可能因命名冲突失败
CREATE 后必须立刻建唯一索引才能用 CONCURRENTLY 刷新
刚建好的物化视图默认不可并发刷新,哪怕你已经确保 GROUP BY 字段业务上唯一。PostgreSQL 不推断、不信任业务逻辑,它只认显式创建的 UNIQUE INDEX。
典型错误流程:
CREATE MATERIALIZED VIEW mv_orders_daily AS SELECT DATE(order_time) AS day, COUNT(*) AS cnt FROM orders WHERE order_time >= '2026-01-01' GROUP BY DATE(order_time); -- ✅ 创建成功 REFRESH MATERIALIZED VIEW CONCURRENTLY mv_orders_daily; -- ❌ 报错:ERROR: cannot refresh materialized view "mv_orders_daily" concurrently, because it does not have a unique index
修复只需两步(顺序不能颠倒):
- 确认分组字段无 NULL:
SELECT COUNT(*) FROM mv_orders_daily WHERE day IS NULL;→ 必须为 0 - 显式建唯一索引:
CREATE UNIQUE INDEX idx_mv_orders_daily_day ON mv_orders_daily (day); - 索引状态需为 VALID:
SELECT indexname, indisvalid FROM pg_indexes WHERE tablename = 'mv_orders_daily';
WITH NO DATA 创建后,CONCURRENTLY 刷新仍会失败
如果用 WITH NO DATA 创建物化视图(即只建结构、不填充),即使后续补了唯一索引,首次 REFRESH ... CONCURRENTLY 依然报错:cannot refresh ... concurrently because the materialized view is not populated。
原因:并发刷新机制依赖“已有数据”作为比对基准,空表无旧数据可比,无法执行差集逻辑。
正确做法只有两种:
- 改用普通刷新启动:
REFRESH MATERIALIZED VIEW mv_orders_daily;(会短暂锁表,但只一次) - 或创建时就用默认
WITH DATA(推荐),再立即建索引
没有“跳过首次全量”的捷径。试图绕过会导致后续所有并发刷新都不可用。
高频率刷新场景下,并发刷新反而更慢且易出错
当物化视图日增数据量超过总量 5%,或单次差异行数 > 10 万时,CONCURRENTLY 的差集计算开销(Hash Join + Sort + 两次索引扫描)会显著高于全量刷新。
典型症状:
-
REFRESH ... CONCURRENTLY执行时间从 1 秒飙升至 30+ 秒 -
pg_stat_activity显示大量Hash Join和Sort状态 - 磁盘 I/O 持续满载,临时空间暴涨
此时应主动降级策略:
- 改用非并发刷新:
REFRESH MATERIALIZED VIEW mv_orders_daily; - 配合低峰期调度(如凌晨 2 点),用
pg_cron或外部任务器触发 - 刷新前先
ANALYZE mv_orders_daily;,避免统计信息陈旧导致计划退化
真正容易被忽略的是:并发刷新不是“性能优化开关”,而是“可用性保障开关”——它解决的是锁表阻塞问题,不是速度问题。是否启用,得看业务对查询中断的容忍度,而非单纯追求快。

















