必须先满足三个条件:物化视图已填充数据、存在VALID唯一索引、索引列全非空;否则REFRESH MATERIALIZED VIEW CONCURRENTLY必报错,因PostgreSQL需该索引比对新旧行。

必须用 REFRESH MATERIALIZED VIEW CONCURRENTLY,但光加这个关键字远远不够——它会直接报错退出,除非你提前满足三个硬性条件:物化视图已填充数据、有 VALID 状态的唯一索引、所有索引列非空。
为什么 REFRESH MATERIALIZED VIEW CONCURRENTLY 一执行就报错
错误信息几乎固定为:ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index。这不是权限或语法问题,而是 PostgreSQL 在执行前做的强制校验:它需要用唯一索引做新旧行比对,没索引就无法识别“哪条是新增、哪条该更新”,干脆拒绝运行。
- 即使源表主键完整,也必须在物化视图上显式建索引:
CREATE UNIQUE INDEX ON mv_orders (order_id); - 如果
order_id允许 NULL,得先兜底:CREATE UNIQUE INDEX ON mv_orders (COALESCE(order_id, 0)); - 刚用
CREATE MATERIALIZED VIEW ... WITH NO DATA创建的空视图,CONCURRENTLY会直接失败,必须先用普通刷新填一次:REFRESH MATERIALIZED VIEW mv_orders;
如何让刷新真正“延迟”且不卡业务查询
并发刷新本身不锁表,但默认仍是同步阻塞调用——你执行完那条 SQL,客户端才返回。要实现“延迟执行”,得把刷新动作交给数据库内调度器,而不是应用层轮询或外部 cron。
- 用
pg_cron扩展最稳妥:SELECT cron.schedule('refresh_hourly', '0 * * * *', 'REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sales_summary;'); - 必须确保
shared_preload_libraries = 'pg_cron'已写入postgresql.conf并重启服务 - 普通用户若需调度,得额外授权:
GRANT USAGE ON SCHEMA cron TO app_user;,并设pg_cron.enable_superuser_commands = off - 别用触发器模拟延迟——
FOR EACH STATEMENT触发器虽能响应 DML,但高频写入下会引发刷新风暴,反而拖垮性能
CONCURRENTLY 刷新时容易被忽略的写冲突
它允许读,但不是完全无感。真实线上常遇到两类静默失败:
- 刷新过程中,业务 INSERT 或 UPDATE 恰好撞上新数据的唯一键,直接报:
ERROR: duplicate key value violates unique constraint - 刷新最后阶段尝试锁定某行做更新时,发现已被业务事务改过,报:
could not lock updated tuple in materialized view - 每次刷新都会生成临时快照,大数据量下磁盘空间压力明显——别设成每分钟刷一次,尤其当物化视图超千万行时
- 性能上通常比普通刷新慢 2–3 倍,因为要全量查源表 + 差集计算 + 两次索引扫描;延迟策略本质是用时间换稳定性,不是换速度
真正难的从来不是写那条 REFRESH 语句,而是设计物化视图时就定好索引结构:选天然唯一、不可为空、极少变更的字段组合,比如 (tenant_id, event_id),而不是 name 或带函数的表达式。

















