oracle物化视图无真正离线刷新,所谓“离线”实为on prebuilt table+atomic_refresh=>false+分区手动控制;complete刷新默认truncate+insert,fast刷新依赖基表日志,仅on prebuilt table可实现可控并行dml与伪离线增量更新。

不能直接“离线刷新”,Oracle 没有离线模式;所谓“离线效果”靠的是 ON PREBUILT TABLE + ATOMIC_REFRESH => FALSE + 分区级手动控制,把刷新从在线事务中剥离出来。
为什么物化视图没有真正的离线刷新?
Oracle 的 DBMS_MVIEW.REFRESH 总是在数据库实例内执行 DML(INSERT/DELETE/TRUNCATE),哪怕设 ATOMIC_REFRESH => FALSE,也只是跳过事务包装,并非脱离数据库运行。所谓“离线”,实际是指:不阻塞业务查询、不依赖实时日志、不卷入主表并发写入链路。
- COMPLETE 刷新默认走 TRUNCATE + INSERT,但若没关原子性,会锁表、记大量 UNDO,等同于在线重刷
- FAST 刷新必须连通基表和 MLOG$,天然在线,无法隔离
- 真正能“脱钩”的只有 ON PREBUILT TABLE 场景:底层是普通表,刷新逻辑由你完全掌控
ON PREBUILT TABLE 是亿级分区刷新的唯一可行起点
对 TB 级分区表,别指望 CREATE MATERIALIZED VIEW ... REFRESH FAST 一次性成功。必须先建好结构一致的物理表,再把它“注册”为物化视图——这样刷新就变成对一张普通表的并行 DML 操作,可控性陡增。
- 先建目标表:
CREATE TABLE mv_sales_part AS SELECT * FROM sales WHERE 1=0,然后手动按sale_date分区(如PARTITION BY RANGE(sale_date)) - 加索引、约束、统计信息,确保与后续 MV 查询路径一致
- 注册为物化视图:
CREATE MATERIALIZED VIEW mv_sales_part ON PREBUILT TABLE REFRESH FAST ON DEMAND AS SELECT ... FROM sales - 首次填充用并行 INSERT:
INSERT /*+ APPEND PARALLEL(8) */ INTO mv_sales_part SELECT ... FROM sales PARTITION (P202609)
如何用分区剪枝实现“伪离线”增量更新?
既然不能传分区名给 DBMS_MVIEW.REFRESH,就绕过它——直接操作底层表,再触发最小粒度的 FAST 刷新。关键在利用分区 ROWID 范围 + SNAPTIME$$ 对齐,让刷新只扫目标分区变更。
- 查目标分区物理范围:
SELECT MIN(ROWID), MAX(ROWID) FROM sales PARTITION (P202609) - 构造日志过滤谓词:
WHERE mview_log_rowid BETWEEN :min_rid AND :max_rid AND snaptime$$ > TO_DATE('2026-09-17 00:00:00', 'YYYY-MM-DD HH24:MI:SS') - 刷新前手动清理旧日志:
EXEC DBMS_MVIEW.PURGE_LOG('SALES', 1),避免百万级扫描 - 调用时强制非原子:
DBMS_MVIEW.REFRESH(list => 'MV_SALES_PART', method => 'F', atomic_refresh => FALSE),它会直接 truncate 分区再 append,不走日志全扫
最容易被忽略的三个硬点
不是参数调得细就能跑通,以下三点缺一不可,且任一失效都会静默退化为 COMPLETE 或卡死:
- PCT 属性必须启用:
CREATE MATERIALIZED VIEW ... ENABLE QUERY REWRITE PCT,否则 Oracle 不识别分区键关联 - 基表分区键必须单列、RANGE/LIST 类型,且 MV 定义中
SELECT和GROUP BY都显式写该列(不能用别名,不能套函数) -
MLOG$_SALES必须含ROWID和SEQUENCE$$,且已建复合索引idx_mlog_seq ON MLOG$_SALES (snaptime$$, sequence$$)—— 没这个索引,哪怕只刷一个分区,照样全表扫日志











