根本原因是默认atomic_refresh=>true强制全量事务刷新,引发基表、日志表及物化视图表的高粒度锁;改用atomic_refresh=>false或concurrently可降级锁,但须满足fast支持、唯一索引等前提条件。

高并发下频繁刷新物化视图锁表,根本原因不是“刷新”本身,而是默认刷新方式强制使用独占锁或长事务,把基表、日志表甚至物化视图自身变成串行瓶颈。关掉 ATOMIC_REFRESH 或加 CONCURRENTLY 是最直接有效的解法,但必须满足前提条件,否则只是把问题从“锁表”换成“报错”或“更慢”。
Oracle 默认 atomic_refresh => TRUE 怎么锁住整张基表
Oracle 调用 DBMS_MVIEW.REFRESH 时,默认 atomic_refresh => TRUE,它会走「DELETE + INSERT」全量路径,并全程包裹在一个事务里。这个事务要保证原子性,就必须在基表、物化视图日志(如 MLOG$_ORDER)、物化视图本身三者上都加高粒度锁:
- 基表上可能触发
enq: TX - row lock contention,尤其当其他会话正在批量 UPDATE 同一范围主键时 - 物化视图日志表被持锁期间,所有对源表的 DML 都得排队等日志写入完成
- 物化视图表上出现
library cache lock,导致所有 SELECT 该 MV 的查询卡在等待状态,v$session.event显示为library cache lock - undo 表空间压力陡增,大刷新可能触发
ORA-01555或事务回滚失败
PostgreSQL 不加 CONCURRENTLY 就等于拒绝并发读
PostgreSQL 的 REFRESH MATERIALIZED VIEW 默认拿 ACCESS EXCLUSIVE 锁——这是最高级别锁,连 SELECT 都不允许。只要刷新没结束,应用查这个物化视图就会一直卡在 wait_event_type = 'Lock',pg_stat_activity.state 还是 active,但实际毫无响应。
- 哪怕只刷新 5 万行,底层执行的原始查询若含
JOIN或GROUP BY,耗时超过 1 秒就足以让 Web 请求超时 -
CONCURRENTLY是唯一绕过此锁的选项,但它不是开关,而是一组硬约束:必须有显式UNIQUE或PRIMARY KEY索引;索引列全为NOT NULL;索引必须覆盖所有GROUP BY列 - 缺任意一条,执行直接报错:
ERROR: cannot refresh materialized view "xxx" concurrently, because it does not have a unique index
为什么关了 ATOMIC_REFRESH 还会被“卡住”,却查不到锁
设 atomic_refresh => FALSE 后,Oracle 改用 TRUNCATE + INSERT /*+ APPEND */,确实不走事务、不争行锁,但仍有隐性阻塞点:
-
TRUNCATE是 DDL,仍需短暂持有DDL lock,虽然比library cache lock时间短,但若恰逢大量解析 SQL,仍可能排队 - 刷新过程虽快,但物化视图会短暂为空——应用层若没做空值兜底,可能直接报错,误以为“锁住了”
- 真正耗时来自底层查询:如果
SELECT部分本身要扫描数百万行、走不到索引、或跨 DBLink 拉数据,那瓶颈根本不在锁,而在查询执行本身;此时v$locked_object干净,但v$session.event是db file sequential read或SQL*Net message from dblink - PostgreSQL 的
CONCURRENTLY刷新虽不阻塞读,但会显著拖慢速度(实测慢 3~5 倍),且最后 merge 阶段可能因业务更新某行而失败:could not lock updated tuple in materialized view
真正关键的不是“怎么刷”,而是“谁在刷、刷什么、刷多久”。user_mviews.fast_refreshable 必须是 'FAST' 才能放心开 atomic_refresh => FALSE;PostgreSQL 的 CONCURRENTLY 前必须 CREATE UNIQUE INDEX;而任何环境下,都得先确认底层查询是否已优化到毫秒级——否则锁不锁表,都救不了响应延迟。










