加CONCURRENTLY是唯一避免读锁的方案,但需满足三个硬条件:物化视图上必须显式创建UNIQUE/PRIMARY KEY索引、索引列全为NOT NULL(NULL需COALESCE处理)、索引覆盖全部分组列;缺一则报错拒绝执行,且刷新期间仍可能因唯一键冲突或行被业务更新而失败。

加 CONCURRENTLY 是唯一能避免读锁的方案,但不是“加了就完事”——它依赖唯一索引、容忍短暂不一致、且仍会阻塞写操作。
为什么 REFRESH MATERIALIZED VIEW 会卡住 SELECT
不带 CONCURRENTLY 的刷新会拿 ACCESS EXCLUSIVE 锁,整个物化视图在刷新期间对所有 SELECT 不可见。你看到的不是慢,是连接卡在 wait_event_type = 'Lock',pg_stat_activity 里状态还是 'active'。哪怕只刷 10 万行,只要底层查询含 JOIN 或聚合,几秒就能让应用超时。
CONCURRENTLY 刷新必须满足的三个硬条件
- 物化视图上必须有显式创建的
UNIQUE或PRIMARY KEY索引(基表主键不会自动继承到 MV 上) - 索引列全部为
NOT NULL;如果源字段允许 NULL,得用COALESCE(col, 0)包裹后建索引 - 若 MV 定义含
GROUP BY a, b,索引必须覆盖全部分组列,只建(a)会导致比对逻辑错乱
缺任意一条,执行直接报错:ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index。这不是警告,是拒绝执行。
CONCURRENTLY 下仍要小心的干扰点
它不阻塞 SELECT,但不是零代价:
- 仍持有
SHARE UPDATE EXCLUSIVE锁,所以对物化视图本身的DROP、TRUNCATE或ALTER会被阻塞 - 刷新期间业务写入若触发唯一键冲突(比如新数据和即将插入的行 key 相同),会报
ERROR: duplicate key value violates unique constraint - 最后 merge 阶段若某行刚被业务
UPDATE过,会失败并提示:could not lock updated tuple in materialized view - 每次刷新多占一倍磁盘空间存临时副本,高频刷新(如每分钟)容易撑爆
pg_wal或base目录
性能与一致性取舍的关键细节
CONCURRENTLY 实际更慢:它先全量跑一遍原始查询生成新结果集,再靠唯一索引逐行比对旧数据,做 INSERT/UPDATE/DELETE。400 万行实测,普通刷新 30s,CONCURRENTLY 常超 150s,I/O 和 CPU 明显抬高。更隐蔽的是——它不减少基表压力,原始 SELECT 仍完整执行一遍。
刷新过程中,SELECT 读到的可能是部分更新、部分未更新的混合状态。业务逻辑必须接受这种最终一致性,不能假设某次查询返回的是“某个时间点的完整快照”。

















