增量刷新本身不直接导致死锁,但会显性化、高频化基表行级锁冲突;FAST刷新需读日志并回放DML,对每行加TX锁,与未提交业务事务“撞车”即满足死锁四要素。

增量刷新本身不直接导致死锁,但会把基表的行级锁冲突显性化、高频化——死锁概率上升,本质是刷新逻辑与业务 DML 在同一行上“撞车”了。
FAST 刷新必须读日志并回放 DML,天然引入行锁竞争
Oracle 的 FAST 刷新不是“只改几行”的轻量操作,而是:读取 MLOG$_xxx 里的变更记录 → 对基表执行对应 INSERT/UPDATE/DELETE。这个过程会对每条涉及的基表行加 TX 行锁。
- 如果业务事务正对某行执行
UPDATE ... WHERE id = 123但尚未提交,而刷新线程恰好要基于日志对该行做DELETE,双方就会互等 -
MLOG$_xxx本身是普通堆表,高并发写入时多个刷新会话还可能争抢日志表的行锁(enq: TX - row lock contention),进一步拖慢刷新进度,拉长锁持有窗口 - 即使基表只有单行更新,只要刷新和业务修改同一行,且事务边界不重叠,就满足死锁四要素(互斥、持有并等待、不可剥夺、循环等待)
atomic_refresh=TRUE 是默认但最危险的配置
默认值 atomic_refresh => TRUE 把整个 FAST 刷新包在一个事务里:先清理旧变更状态,再逐条回放日志。一旦中间卡住,所有已加的 TX 锁会一直持有着,直到事务超时或被杀。
- 它不会降低锁粒度,只是让锁“更持久”——百万行日志可能让一个事务持锁几十秒
- undo 表空间压力陡增,容易触发
ORA-01555或直接 hang 住,间接放大死锁发生率 - 正确做法是显式传参:
DBMS_MVIEW.REFRESH('MV_NAME', atomic_refresh => FALSE),让 Oracle 按日志批次分小事务提交,每批只锁当前处理的几行
日志表统计信息陈旧会误导执行计划,延长锁时间
SELECT * FROM user_mview_logs WHERE master = 'SALES' 只能确认日志存在,不能反映其活跃程度。若 MLOG$_SALES 的统计信息过期,CBO 可能误判为小表,选择嵌套循环(NL)而非哈希连接,导致刷新 SQL 执行变慢,TX 锁自然挂得更久。
- 定期收集日志表统计信息:
DBMS_STATS.GATHER_TABLE_STATS('SCHEMA', 'MLOG$_SALES') - 避免在源表 DDL(如新增列、分区 split)后立即刷新——此时 Oracle 可能自动重建日志表,触发基表 TM 锁(模式 6),阻塞所有 DML
- 查
v$session时别只盯event,重点看blocking_session和last_call_et:若阻塞会话last_call_et > 3600且status = 'INACTIVE',大概率是未提交的长事务,比刷新本身更该优先处理
真正难缠的不是刷新逻辑,而是业务事务与刷新在行粒度上的不可见耦合——你永远不知道下一条日志要改哪一行,也不知道那行此刻正被哪个应用线程攥着没放手。

















