refresh materialized view 不带 concurrently 会持 access exclusive 锁致 select 完全阻塞;加 concurrently 可避免读锁但需满足三前提:显式唯一索引、索引列全 not null、索引覆盖全部 group by 列,否则报错;其刷新更慢、占双倍磁盘、不减基表压力且返回混合状态。

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










