物化视图日志本身不锁基表,fast刷新时才对基表变更行加tx行锁;锁冲突源于刷新读取日志并回放dml,而非日志存在本身;atomic_refresh=false可缩短持锁时间但不改变行锁本质。
物化视图日志(mlog$_xxx)本身不锁基表,但刷新时会争抢日志表的行锁
物化视图日志是普通堆表(如 mlog$_sales),它自身不会主动锁定源表(如 sales)。真正引发基表行级阻塞的,是 dbms_mview.refresh 执行 fast 刷新时的 dml 行为:它要读取日志表里的变更记录,再对基表执行对应 update/delete/insert。这个过程会对基表上被修改的**具体行**加行锁(tx 锁),不是表锁。
常见误解是“日志表一建就锁基表”,其实日志表只是记录变更的载体;锁发生在刷新动作读取日志并回放时。
- 日志表被大量写入(比如源表每秒数百次 DML),而刷新又频繁触发 → 日志表本身可能被多个刷新会话争抢,出现
enq: TX - row lock contention,表现为刷新卡住 - 基表某行正被业务 UPDATE 未提交,而刷新线程恰好要基于日志对该行做 DELETE → 双方互等,形成死锁(Oracle 通常自动选中刷新会话为牺牲者)
-
SELECT * FROM user_mview_logs WHERE master = 'SALES'能确认日志是否存在,但不能反映其是否“活跃”——若日志表统计信息陈旧,CBO 可能误判执行计划,拖慢刷新,间接延长持锁时间
FAST 刷新期间基表的锁粒度取决于 atomic_refresh 和刷新类型
锁行为不是由“有没有日志”决定,而是由刷新方式和事务边界共同控制:
- 当
atomic_refresh => TRUE(默认)且走 FAST 刷新:Oracle 仍会在刷新事务内对涉及的基表变更行加 TX 锁,但整个刷新操作包裹在一个事务里 → 若刷新中途失败,所有变更回滚,锁持续到事务结束 - 当
atomic_refresh => FALSE且走 FAST 刷新:Oracle 拆成多个小事务(例如按日志批次 commit),每批只锁当前处理的几行 → 锁持有时间大幅缩短,降低冲突概率 - 但注意:
atomic_refresh => FALSE不改变“行锁”本质,只是把长事务切短;它无法避免单行被业务与刷新同时修改导致的冲突
如何确认当前刷新正在锁哪些基表行
不能只查 v$locked_object(它只显示表级锁对象),得深入到行级锁链路:
- 先定位刷新会话:
SELECT sid, serial#, sql_id FROM v$session WHERE program LIKE '%DBMS_MVIEW%' - 查该会话持有的 TX 锁:
SELECT l.sid, l.type, l.lmode, l.request, s.sql_id FROM v$lock l, v$session s WHERE l.sid = s.sid AND l.type = 'TX' AND s.sid = &sid - 反查被锁的具体行(需有权限):
SELECT do.object_name, t.xidusn, t.xidslot, t.xidsqn, r.row_wait_obj#, r.row_wait_file#, r.row_wait_block#, r.row_wait_row# FROM v$transaction t, v$session s, v$session_wait w, v$locked_object l, dba_objects do, v$session r WHERE s.sid = &sid AND s.taddr = t.addr AND s.sid = w.sid AND w.event = 'enq: TX - row lock contention' AND s.sid = l.session_id AND l.object_id = do.object_id - 如果
row_wait_row#非空,说明锁已落到具体行;结合dbms_rowid.rowid_create可构造出实际 ROWID 定位数据
最容易被忽略的隐性锁源:物化视图日志的维护操作
很多人只盯着刷新本身,却忘了日志表自己也会被维护操作锁定:
- 当源表发生 DDL(如新增列、分区 split),Oracle 自动清理或重建日志表 → 触发
ALTER TABLE MLOG$_SALES ...,这会拿基表的 TM 锁(模式 6),阻塞所有 DML - 日志表上的索引失效后未重建,FAST 刷新被迫全表扫描日志 → 持锁时间指数增长,业务 UPDATE 等在 undo 上等待 read consistency
-
DBMS_MVIEW.PURGE_LOG手动清理日志时若没指定num_rows或用错条件,可能锁住整个日志表数分钟,间接让后续刷新排队等待
基表行锁看似局部,但源头常藏在日志表结构、统计信息、维护时机这些不起眼的地方。别只盯着 DBMS_MVIEW.REFRESH 的参数,得把日志表当成一个独立的、会参与并发竞争的业务表来看待。











