物化视图刷新冲突主因是调度重叠与隐式依赖未对齐,而非锁表本身;需错开job时间、显式声明刷新组依赖或改用job链控制时序,并及时处理broken状态job。
物化视图刷新冲突多数不是锁表本身的问题,而是多个刷新任务在时间上重叠、争抢同一资源(如 ji enqueue、基表行锁、undo 空间或 mlog$_ 表消费权),直接调大 job 间隔或硬塞 sleep 并不能根治——关键得让调度逻辑和刷新行为对齐真实依赖关系。
DBA_JOBS 中刷新 JOB 时间重叠导致 JI-contention
Oracle 对单个物化视图的刷新会持有一个 JI enqueue(用于串行化同一 MV 的并发刷新),如果两个 DBMS_JOB 作业被调度到同一秒内执行(尤其用 SYSDATE + 1/24 这类粗粒度间隔),后启动的那个就会卡在 enq: JI - contention 上,v$session_wait 显示等待,dba_jobs_running 可能只看到一个 SID 在跑。
- 查重叠作业:
SELECT job, what, next_date, interval FROM dba_jobs WHERE what LIKE '%dbms_mview.refresh%' ORDER BY next_date;—— 注意看next_date是否密集扎堆 - 避免硬编码固定间隔:别用
'SYSDATE + 1/24',改用'TRUNC(SYSDATE) + 2/24 + (job_id * 5)/1440'给每个 JOB 加偏移(单位分钟),错开启动窗口 - 已卡住时别等:查出持有 JI 的会话:
SELECT sid, program FROM v$session WHERE event = 'enq: JI - contention';,再结合dba_jobs_running定位源头;若确认是异常僵死进程(如 STATUS = 'KILLED' 但未清理),需ALTER SYSTEM KILL SESSION 'sid,serial#'
刷新组(REFRESH GROUP)内 MV 执行顺序引发隐式依赖冲突
用 DBMS_REFRESH.MAKE 建的刷新组默认按 MV 创建顺序执行,但如果组内某 MV 依赖另一个 MV 的结果(比如 A 聚合基表,B 再聚合 A),而刷新组没显式声明依赖,Oracle 仍会并行刷——导致 B 读到 A 的中间态数据,报 ORA-12008 或约束冲突。
- 检查组内依赖:
SELECT rname, mview_name, broken FROM dba_refresh_children WHERE rname = 'YOUR_GROUP_NAME';——broken = 'YES'往往是顺序错乱信号 - 重建组时强制顺序:
DBMS_REFRESH.DESTROY('GRP_A'); DBMS_REFRESH.MAKE( rname => 'GRP_A', list => 'MV_A, MV_B', next_date => SYSDATE, interval => 'SYSDATE+1/24' );不够,必须补:DBMS_REFRESH.ADD( rname => 'GRP_A', list => 'MV_B', lax => TRUE );并确保MV_B定义中FROM MV_A被优化器识别为可重写对象(即ENABLE QUERY REWRITE) - 更稳的做法:拆组,用 JOB 链控制时序,例如 JOB1 刷 MV_A → JOB1 结尾调用
DBMS_JOB.SUBMIT启动 JOB2 刷 MV_B
DBMS_JOB 无失败重试机制,一次失败就停摆成“伪冲突”
很多所谓“刷新冲突”,其实是某个 JOB 因日志缺失、STALENESS=UNUSABLE 或 ORA-00060 死锁失败后,被 Oracle 自动设为 BROKEN = 'Y',next_date 变成 DATE '4000-01-01',后续所有依赖它的下游刷新(包括同组其他 MV)都因等不到前置结果而超时或卡住。
- 先筛出真问题 JOB:
SELECT job, broken, failures, last_date, next_date FROM dba_jobs WHERE what LIKE '%refresh%' AND (broken = 'Y' OR failures > 0); - 重置前必查三件事:
SELECT staleness, refresh_method FROM dba_mviews WHERE mview_name = 'XXX';(确认不是 UNUSABLE)、SELECT COUNT(*) FROM mlog$_base_table;(防日志堆积)、SELECT object_name, locked_mode FROM v$locked_object JOIN dba_objects USING(object_id) WHERE object_name IN ('XXX');(清残留锁) - 重置命令要带时间:
BEGIN DBMS_JOB.BROKEN(job => 123, broken => FALSE, next_date => SYSDATE + 1/1440); END;—— 若只设FALSE不设next_date,下次仍可能跳过
真正难处理的从来不是单次刷新慢,而是多个定时任务之间看不见的依赖链和状态漂移。JOB 的 next_date 是计划时间,不是保证时间;刷新组的 list 参数只是声明集合,不等于执行拓扑。任何想靠“调大间隔”或“重启 JOB”解决冲突的尝试,都绕不开先看清谁在等谁、谁卡住了什么资源。











