物化视图不支持直接基于 union all 视图进行查询重写,因其缺乏单源语义可推导性;必须将 union all 逻辑“下沉”至物化视图定义中,显式列名、统一 null 性与数据类型,并配齐刷新日志、刷新策略及复合索引。
物化视图不支持直接基于 union all 视图进行查询重写
oracle 19c 的查询重写(query rewrite)机制默认不识别由 union all 构成的视图作为重写候选对象——哪怕该视图结构简单、字段类型一致、无聚合,只要定义里含 union all,优化器就大概率跳过它。这不是 bug,是设计限制:重写器要求“单源语义可推导”,而 union all 视图在逻辑上等价于多表并集,缺乏统一的行来源标识,无法安全映射到物化视图的刷新日志和键约束。
常见错误现象包括:EXPLAIN PLAN 显示仍走基表全扫;DBMS_MVIEW.EXPLAIN_REWRITE 返回 UNREWRITTEN 或 FAILED;即使 QUERY_REWRITE_ENABLED = TRUE 且用户有权限,也无效果。
- 必须确保物化视图定义本身不含
UNION ALL,而是直接从基表 SELECT(或仅含 INNER JOIN) - 如果业务逻辑依赖
UNION ALL视图(如历史表 + 当前表),应把该视图的逻辑“下沉”到物化视图定义中,而非建在视图之上 -
QUERY_REWRITE_INTEGRITY若设为ENFORCED,基表必须有RELY约束(如RELY ENABLE NOVALIDATE PRIMARY KEY),否则静默失败
用物化视图替代 UNION ALL 视图时,字段对齐和 NULL 处理必须显式控制
当把 SELECT * FROM t1 UNION ALL SELECT * FROM t2 这类视图逻辑“平铺”进物化视图定义时,不能直接写 SELECT * FROM t1 UNION ALL SELECT * FROM t2 —— 因为物化视图要求所有列具有确定的、可索引的 NULL 性(NULL/NOT NULL),而 * 会继承基表定义,两表同名列的 NULL 属性可能不一致,导致创建失败或后续重写失效。
实操建议:
- 显式列出所有列,不要用
* - 对可能为 NULL 的列,统一用
CAST(... AS ...)或TO_NUMBER(NULL)、TO_DATE(NULL)显式声明类型与空值性 - 确保两部分
SELECT中对应位置列的数据类型完全兼容(例如都为VARCHAR2(50),而非一个VARCHAR2(30)一个VARCHAR2(100)) - 若某列在 t1 中为 NOT NULL、t2 中为 NULL,物化视图中该列最终为 NULL,需在应用层接受此语义
对 CONNECT BY + UNION ALL 场景,物化视图只能加速底层数据扫描,不能加速递归逻辑
像 SELECT ... FROM (t1 UNION ALL t2) START WITH ... CONNECT BY ... 这类查询,物化视图最多能覆盖括号内数据合并部分,但无法参与 CONNECT BY 的树形遍历过程。也就是说,你可以建一个 MV 预先存好 t1 UNION ALL t2 的结果,让 START WITH 前的扫描变快,但 CONNECT BY 的执行计划仍为 CONNECT BY WITH FILTERING,不会被重写为对 MV 的索引扫描。
关键限制点:
- 物化视图不支持
CONNECT BY语法本身,定义中出现会报错ORA-32313 - 即使 MV 已存在,优化器也不会将
CONNECT BY查询改写为“先查 MV 再递归”,因为递归路径依赖运行时绑定变量和中间结果,无法静态匹配 - 真正有效的做法是:把高频访问的根节点集合(如常用
START WITH INTJOBID = ?的取值)提前物化,并在 MV 上建复合索引(如(INTJOBID, INTPRIORJOBID)),加速初始定位和父子跳转
刷新策略与索引必须手动配齐,否则 MV 形同虚设
建完物化视图只是第一步。没配刷新日志、没设刷新方式、没加索引,它的性能表现可能比原查询还差——因为多了一层存储和维护开销。
必须做的三件事:
- 为每个基表创建带
ROWID和关键列的物化视图日志:CREATE MATERIALIZED VIEW LOG ON t1 WITH ROWID, SEQUENCE (col1, col2) INCLUDING NEW VALUES - 刷新方式选
ON DEMAND并配合DBMS_MVIEW.REFRESH调度,避免ON COMMIT在高并发下锁表;若基表是分区表,优先用REFRESH_FAST_AFTER_INSERT按分区刷 - 立即建复合索引:比如 MV 含
time_id, region, product_line,就执行CREATE INDEX idx_mv_trp ON mv_name (region, time_id),顺序按过滤频次排,等值条件放前,范围放后
最容易被忽略的是索引缺失。MV 是物理表,但 Oracle 不自动建任何索引,也不复用基表索引。没有索引的 MV 在报表 WHERE 条件下就是全表扫描,比原视图还慢。











