DELETE本身不锁物化视图日志表MLOG$_xxx,但FAST刷新时需SELECT...FOR UPDATE锁定日志行并等待基表TX锁,导致enq: TX行锁争用;ATOMIC_REFRESH=FALSE可缩短锁持有时间,统计信息过期会加剧锁等待。

基表并发 DELETE 不会直接锁死物化视图日志,但会通过 FAST 刷新时的行锁争抢和日志读取阻塞,间接导致日志表(MLOG$_xxx)被多个刷新会话卡在 enq: TX - row lock contention 上。
为什么 DELETE 本身不锁日志表,却让日志“动不了”
基表上的 DELETE 操作只会在事务提交前对被删行加 TX 行锁,不会触碰 MLOG$_xxx。真正出问题的是后续的 FAST 刷新:它要扫描日志表里对应的 DEL 标记行,再对基表执行反向操作(比如补一条逻辑删除标记)。这个过程需要:
- 从
MLOG$_xxx中SELECT ... FOR UPDATE读取变更记录 → 拿日志表的行锁 - 根据日志内容对基表做
UPDATE或DELETE→ 拿基表对应行的 TX 锁 - 若基表某行正被业务
DELETE未提交,刷新线程就会等那把 TX 锁;而日志表里那条记录又被其他刷新会话先锁住 → 双重等待形成链式阻塞
DBMS_MVIEW.REFRESH 在并发 DELETE 下的锁行为差异
默认 ATOMIC_REFRESH => TRUE 会让整个刷新包在一个事务里跑到底,哪怕只处理 1 条日志,也会全程持有日志表那行的锁,直到刷新结束。一旦基表 DELETE 长时间未提交,这个锁就一直挂着。
改用 ATOMIC_REFRESH => FALSE 后:
- 刷新拆成小批次(如每次处理 100 条日志),每批 commit 后立刻释放锁
-
MLOG$_xxx行锁只持几毫秒,大幅降低被卡概率 - 但注意:它不解决“基表行正被删”的冲突,只是不让一把锁堵死整张日志表
如何快速确认是不是日志表被刷死了
别只查 v$locked_object 看有没有锁——它可能只显示基表,漏掉日志表本身的行锁。要直击关键:
- 查阻塞源头:
SELECT sid, serial#, sql_id, event, blocking_session FROM v$session WHERE event LIKE 'enq: TX%' - 定位日志表锁行:
SELECT do.object_name, lo.xidusn, lo.xidslot, lo.xidsqn FROM v$locked_object lo, dba_objects do WHERE lo.object_id = do.object_id AND do.object_name LIKE 'MLOG$_%' - 如果返回多行且
xidusn相同,说明多个刷新会话在抢同一日志批次,基本可断定是并发 DELETE + FAST 刷新引发的资源争抢
最容易被忽略的点:日志表统计信息过期放大问题
MLOG$_xxx 是普通堆表,CBO 依赖其统计信息选执行计划。如果长期没 DBMS_STATS.GATHER_TABLE_STATS,优化器可能误判为“小表”,对日志扫描走嵌套循环+全表扫,导致单次刷新耗时暴涨,锁持有时间被动拉长。这不是锁机制问题,而是性能拖累锁表现——很多“锁死”现象其实源于这里。


















