DBMS_JOB无法高效刷新多分区物化视图,因其调用DBMS_MVIEW.REFRESH时全量扫描MLOG$表且不感知分区;须改用自定义PL/SQL+显式日志过滤,并推荐DBMS_SCHEDULER替代。

不能只靠 START WITH/NEXT 实现“多个分区”的定时高效刷新——它连单个物化视图都刷不起来,更别说分区粒度控制。
为什么 DBMS_JOB 刷新多个分区物化视图总卡在 MLOG$ 扫描?
根本原因是 DBMS_JOB(或旧式 DBA_JOBS)只负责调用 DBMS_MVIEW.REFRESH,而该函数默认全量扫描整个物化视图日志表(MLOG$_SALES),不感知分区。当基表是百万级分区表、日志堆积到几百万行时,ORDER BY sequence$$, snaptime$$ 就会触发磁盘排序,CPU 和 I/O 直接拉满。
- 查证方式:
SELECT sql_text FROM v$session s, v$sqlarea a WHERE s.sql_address = a.address AND s.event = 'direct path write temp'—— 如果看到大量SELECT ... FROM MLOG$_*正在写临时表,就是它 - 关键陷阱:即使你建了分区键索引、开了
PCT,只要没在刷新逻辑里显式过滤日志行,Oracle 就不会自动按分区裁剪扫描范围 - 日志表没复合索引?立刻执行:
CREATE INDEX idx_mlog_snap_seq ON MLOG$_SALES (snaptime$$, sequence$$)
如何让定时 JOB 真正只刷指定分区(比如 P202609)?
必须绕过 DBMS_MVIEW.REFRESH 的黑盒逻辑,改用自定义 PL/SQL 块 + 显式日志谓词。核心是:用分区 ROWID 范围 + snaptime$$ 时间戳双条件锁定待处理日志行。
- 先查出目标分区名:
SELECT partition_name FROM user_tab_partitions WHERE table_name = 'SALES' AND high_value LIKE '%2026-09%' - 构造日志过滤子查询:
WHERE mview_log_rowid IN (SELECT ROWID FROM SALES PARTITION (P202609)) AND snaptime$$ > TO_DATE('2026-09-16 02:00:00', 'yyyy-mm-dd hh24:mi:ss') - 在 JOB 中调用自定义过程(不能直接传分区名给
DBMS_MVIEW.REFRESH):BEGIN -- 先更新 SNAPTIME$$ 标记为已处理(模拟快速刷新内部行为) UPDATE MLOG$_SALES SET snaptime$$ = SYSDATE WHERE mview_log_rowid IN (SELECT ROWID FROM SALES PARTITION (P202609)) AND snaptime$$ = TO_DATE('4000-01-01', 'yyyy-mm-dd'); COMMIT; <p>-- 再执行 FAST 刷新(此时日志已被标记,且无其他 MV 干扰) DBMS_MVIEW.REFRESH('MV_SALES_MONTHLY', 'F', atomic_refresh => FALSE); END;
DBMS_SCHEDULER 比 DBMS_JOB 更适合多分区场景的三个硬原因
DBMS_JOB 在 12c+ 已被标记为 deprecated,而 DBMS_SCHEDULER 支持错误保留、依赖链、日志分级,对多分区刷新这种易失败操作更鲁棒。
- 作业失败后能自动重试并记录详细错误:
SELECT log_date, status, additional_info FROM user_scheduler_job_log WHERE job_name = 'REFRESH_P202609' ORDER BY log_date DESC - 可为每个分区创建独立作业(如
REFRESH_P202609、REFRESH_P202610),避免一个失败拖垮全部 - 支持
job_class控制资源组,防止多个分区刷新并发争抢 UNDO 表空间 - 示例创建语句:
BEGIN DBMS_SCHEDULER.CREATE_JOB( job_name => 'REFRESH_P202609', job_type => 'PLSQL_BLOCK', job_action => 'BEGIN your_custom_refresh_proc(''P202609''); END;', start_date => TRUNC(SYSDATE) + 3/24, repeat_interval => 'FREQ=DAILY', enabled => TRUE, comments => 'Refresh only partition P202609' ); END;
ATOMIC_REFRESH=FALSE 在多分区定时刷新中要不要开?
要开,但必须搭配事务边界控制——否则一个分区刷新失败,整个物化视图变空,下游所有查询立刻报 ORA-01403: no data found。
- 正确姿势:每个分区刷新封装成独立事务块,在自定义过程中显式加
EXCEPTION捕获,并记录失败分区到日志表 - 绝对不能开的场景:
ON COMMIT物化视图、有外键依赖的下游表、BI 工具直连且无兜底缓存 - 性能收益实测:对 500 万行分区,
ATOMIC_REFRESH => FALSE可将刷新耗时从 42 分钟压到 6 分钟(跳过临时表 + 启用APPEND) - 真正容易被忽略的是:
snaptime$$时间戳精度必须对齐到秒级——如果上次刷新时间是2026-09-16 02:00:00,新刷就必须从这个精确时间之后取日志,差一秒就漏数据


















