根本原因是默认刷新方式强制使用高粒度锁或长事务,将基表、日志表及物化视图自身拖入串行瓶颈;PostgreSQL不加CONCURRENTLY即持ACCESS EXCLUSIVE锁阻塞所有SELECT,Oracle默认atomic_refresh=>TRUE则以DELETE+INSERT全量事务加三重锁,关ATOMIC_REFRESH或改CONCURRENTLY仅降级锁型,未消除阻塞点。

根本原因不是“刷新”动作本身,而是默认刷新方式强制使用高粒度锁或长事务,把基表、日志表、物化视图自身全拖进串行瓶颈。
PostgreSQL 不加 CONCURRENTLY 就拿 ACCESS EXCLUSIVE 锁
REFRESH MATERIALIZED VIEW 默认会申请 ACCESS EXCLUSIVE 锁——这是 PostgreSQL 最强锁级,连 SELECT 都不允许。只要刷新没结束,所有查这个物化视图的请求就卡在 wait_event_type = 'Lock',pg_stat_activity.state 显示为 active,但实际无响应。
- 哪怕只刷 5 万行,原始查询若含
JOIN或GROUP BY,耗时超 1 秒就足以让 Web 请求超时 - 锁持续时间 = 查询执行时间 + 数据写入时间,与数据量基本呈线性增长
- 没有折中选项:不加
CONCURRENTLY,就等于主动拒绝并发读
Oracle 默认 atomic_refresh => TRUE 走 DELETE+INSERT 全量事务
调用 DBMS_MVIEW.REFRESH 时,atomic_refresh => TRUE(默认)会走「建临时表 → 全量插入 → 原子替换」路径,全程包裹在一个事务里。这个事务要保证原子性,就必须在三处加锁:
- 基表上可能触发
enq: TX - row lock contention,尤其当其他会话正在批量UPDATE同一主键范围时 - 物化视图日志表(如
MLOG$_ORDER)被持锁期间,所有对源表的 DML 都得排队等日志写入完成 - 物化视图表上出现
library cache lock,导致所有SELECT该 MV 的查询卡住 - 大刷新还可能引发
ORA-01555(快照太旧)或ORA-30036(UNDO 空间不足)
为什么关了 ATOMIC_REFRESH 或加了 CONCURRENTLY 还是卡
改用非原子刷新或并发刷新,只是降级锁类型,并不消除阻塞点:
-
atomic_refresh => FALSE改走TRUNCATE + INSERT /*+ APPEND */,虽绕过 UNDO,但TRUNCATE是 DDL,仍需短暂持有 DDL lock;若恰逢大量 SQL 解析,照样排队 -
REFRESH MATERIALIZED VIEW CONCURRENTLY不阻塞SELECT,但会持SHARE UPDATE EXCLUSIVE锁,导致DROP、TRUNCATE、ALTER全部被阻塞 - CONCURRENTLY 刷新失败时不会报“慢”,而是直接拒绝执行,错误固定为:
ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index - 它仍需全量跑原始查询 + 差集计算 + 两次索引扫描,400 万行实测比普通刷新慢 2–3 倍,I/O 和 CPU 明显抬高
真正可控的“不锁表”只有一条路:双表切换
想彻底消灭停机窗口,就得放弃“单表刷新”思路:
- 维护两张结构相同的物化视图表(如
mv_sales_v1和mv_sales_v2) - 每次刷新先写入空表(用
CREATE TABLE AS或INSERT /*+ APPEND */),再原子切换ALTER TABLE ... RENAME - 应用层通过视图或配置指向当前生效表,切换瞬间完成,无锁、无不可用期
- 代价是多一倍磁盘空间,且需额外调度逻辑管理生命周期
复杂点不在语法,而在一致性边界:切换瞬间,业务写入是否与新数据冲突?旧表清理是否影响未完成查询?这些细节比锁本身更难测试和覆盖。










