Oracle 物化视图维护总结(原理+用途+案例)

Oracle 物化视图维护总结(原理+用途+案例)

物化视图(Materialized View,简称 MV)是 Oracle 数据库中一项强大的功能。它不同于普通的视图(View)------普通视图只存储 SQL 定义,不存储数据;而物化视图会像表一样,将查询结果 物理存储在数据库中。这一特性使其成为性能优化和数据复制的利器。

一、物化视图原理

1.1 什么是物化视图

物化视图(Materialized View,简称 MV)与普通视图(View)的区别:

对比项 普通视图(View) 物化视图(Materialized View)
数据存储 不存储数据,仅保存 SQL 定义 存储查询结果集,占用磁盘空间
查询方式 每次查询时实时执行底层 SQL 直接读取已存储的结果数据
性能 依赖底层查询效率 避免重复计算,查询极快
数据时效 始终与基表一致 需要刷新才能与基表同步
本质 一个命名的 SQL 语句 一个实体表 + 一个 SQL 定义

物化视图的本质是"预计算并存储查询结果",它把原本需要实时执行的复杂查询结果提前算好、存下来,后续查询直接读这个结果,从而大幅提升性能。

其高效运转依赖于三个核心组件:物化视图本身 、物化视图日志 和刷新机制。

1.2 物化视图的三大核心组件

物化视图的完整工作机制由三个核心组件协同完成:

复制代码
┌──────────────┐       变更记录          ┌──────────────────┐
│   基表(Base   │  ──────────────────>  │  物化视图日志      │
│    Table)    │   INSERT/UPDATE/      │  (MV Log)        │
│              │   DELETE              │  MLOG$_<表名>     │
└──────┬───────┘                       └────────┬─────────┘
       │                                        │
       │  初始数据                                │  增量变更记录
       │  (BUILD IMMEDIATE)                      │
       ▼                                        ▼
┌──────────────────────────────────────────────────────┐
│                    物化视图 (MV)                       │
│   ┌─────────────────────────────────────────────┐    │
│   │  存储查询结果数据(占用磁盘空间)                 │    │
│   │  可通过 REFRESH 机制持续与基表保持同步           │    │
│   └─────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────┘
组件一:物化视图日志(Materialized View Log)

物化视图日志是实现快速刷新(Fast Refresh) 的基础,它保存在与基表相同的用户下 。它是一个与基表(Master Table)关联的特殊跟踪表,用于记录基表上所有 DML 变更(INSERT、UPDATE、DELETE)的条目。

  • 命名规则:MLOG$_<基表名>
  • 本质:一张普通的 Oracle 表,由系统自动维护
  • 作用:为快速刷新(Fast Refresh)提供增量变更数据
  • 清理:正常执行 FAST 刷新后,Oracle 会自动删除所有关联 MV 都已消费的日志记录

其工作原理如下:

  1. 当基表发生 DML 操作时,一个内部触发器会被激活。
  2. 该触发器将受影响行的信息(如 ROWID、主键值或变更列的新旧值)记录到物化视图日志中。
  3. 当物化视图执行快速刷新时,Oracle 会读取这些日志,仅将变更部分应用到物化视图上,从而避免重新执行整个查询。

一个基表只能有一个物化视图日志

物化视图日志的内部关键字段:

字段名 类型 说明
SNAPTIME$$ DATE 标识该日志记录何时被刷新消费,初始值为 4000-01-01
DMLTYPE$$ VARCHAR2(1) 操作类型:I(INSERT)、D(DELETE)、U(UPDATE)
OLD_NEW$$ VARCHAR2(1) O=旧值,N=新值。UPDATE 产生两条记录
SEQUENCE$$ NUMBER 保证 DML 操作的全局顺序
CHANGE_VECTOR$$ RAW 记录哪些列发生了变化(位图编码)
XID$$ NUMBER
组件二:物化视图本身(Materialized View)

物化视图基于一个或多个基表创建,其核心特征是存储数据、占用存储空间。创建时,Oracle 会执行物化视图定义中的 SELECT 查询,将结果集写入一个实际的数据库段(Segment)中。

与普通视图不同,物化视图可以:

  • 建立索引
  • 进行分区
  • 添加约束
  • 进行压缩
  • 被查询优化器用于查询重写(Query Rewrite)
组件三:刷新机制(Refresh Mechanism)

刷新是将基表的变更同步到物化视图的过程。根据刷新方式,可分为:

刷新方式 说明 适用场景
COMPLETE(完全刷新) 重新执行物化视图的定义查询,完全重建数据 首次创建、数据量小、或无法快速刷新时
FAST(快速刷新) 仅应用物化视图日志中的变更,增量更新 基表有物化视图日志,且变更频繁的大表
FORCE(强制刷新) 默认选项。优先尝试 FAST,若失败则自动降级为 COMPLETE 大多数场景,系统自动选择最优方式

根据刷新时机,又可分为:

刷新时机 说明
ON DEMAND 手动触发刷新,为默认值
ON COMMIT 基表事务提交时自动刷新

注意 :ON COMMIT 刷新会略微延长提交时间,适合数据仓库等并发更新不高的场景。

1.3 物化视图日志详解

创建语法
sql 复制代码
-- 基本语法
CREATE MATERIALIZED VIEW LOG ON <表名>
  WITH <ROWID | PRIMARY KEY | OBJECT ID>  -- 行标识方式
  [, SEQUENCE(列名1, 列名2, ...)]         -- 序列号及列列表
  INCLUDING NEW VALUES;                    -- 记录更新后的新值
关键参数说明
  • WITH ROWID:日志中记录被修改行的 ROWID,支持更多类型的物化视图快速刷新
  • WITH PRIMARY KEY:日志中记录被修改行的主键值,更常用、更稳定
  • SEQUENCE :提供事务顺序号,确保同一行的多次更新能按正确顺序应用,是快速刷新的必需项
  • 列列表:列出物化视图查询中引用的所有列(包括 WHERE 条件列、JOIN 列、聚合源列)
  • INCLUDING NEW VALUES:记录 UPDATE 操作后的新值。没有此选项,日志只存旧值,INSERT 和 UPDATE 后的新数据进不了日志,刷新时只能全量重算
示例
sql 复制代码
-- 在 SH 模式的 TIMES 表上创建物化视图日志
CREATE MATERIALIZED VIEW LOG ON sh.times
  WITH ROWID, SEQUENCE(prod_id, time_id, amount_sold)
  INCLUDING NEW VALUES;

-- 创建基于主键跟踪的物化视图日志
CREATE MATERIALIZED VIEW LOG ON scott.emp
  TABLESPACE users
  WITH PRIMARY KEY
  INCLUDING NEW VALUES;

重要提醒 :SEQUENCE 和 INCLUDING NEW VALUES 缺一不可。没有 SEQUENCE,Oracle 无法排序同一行的多次更新,FAST 刷新直接拒绝执行;没有 INCLUDING NEW VALUES,日志只存旧值,刷新时会静默退化为 COMPLETE 全量刷新。


1.4 两个相关系统参数

1)QUERY_REWRITE_ENABLED参数

QUERY_REWRITE_ENABLED 参数有 false 、true 和 force 三个可选值。

这个参数是动态 的,可以在系统级(ALTER SYSTEM) 或会话级(ALTER SESSION) 进行修改。

它的三个值的具体含义如下:

值 含义
false 禁用查询重写。优化器不会考虑将查询重写为使用物化视图。
true 启用查询重写,并进行成本比较 。优化器会同时评估"使用查询重写"和"不使用查询重写"两种执行计划的成本,并选择成本更低的那一个 。这是10g及以上版本的默认值。
force 强制使用查询重写 。优化器将始终 尝试使用查询重写,并且不再进行成本比较。如果你确认查询重写总能带来性能提升,或希望减少编译时间,可以考虑使用此模式。
  • 查看当前值:

    复制代码
    SHOW PARAMETER QUERY_REWRITE_ENABLED;
  • 修改设置(例如,强制启用):

    复制代码
    -- 会话级别修改
    ALTER SESSION SET QUERY_REWRITE_ENABLED = force;
    
    -- 系统级别修改(影响所有新会话)
    ALTER SYSTEM SET QUERY_REWRITE_ENABLED = force;

2)QUERY_REWRITE_INTEGRITY参数

QUERY_REWRITE_INTEGRITY 参数用于控制查询重写(Query Rewrite)的一致性级别,也就是在多大程度上允许优化器为了性能而放宽结果的精确性。

这个参数有三个可选值,严格程度从高到低依次为:ENFORCED (强制)、TRUSTED (可信) 和 STALE_TOLERATED (容忍陈旧)。

值 (Value) 核心行为 数据新鲜度要求 约束/关系要求 主要用途与风险
ENFORCED 最安全、最严格。只使用能保证结果绝对正确的重写。 物化视图数据必须最新 (Fresh)。 依赖的约束关系必须被Oracle强制生效 (Enforced) ,如 VALIDATED 状态。 默认值。保证结果绝对准确,但可能错失一些性能优化机会。
TRUSTED 中等严格。信任用户声明但未强制验证的关系。 物化视图数据必须最新 (Fresh)。 允许使用 RELY 关键字声明但处于 NOVALIDATE 状态的约束。 适用于数据质量可靠,但未启用强制约束的环境,在准确性和性能间取得平衡。
STALE_TOLERATED 最宽松、性能最高。为追求性能可牺牲数据实时性。 允许 物化视图数据陈旧 (Stale),与基表不一致。 允许使用任何声明的关系,无论是否被Oracle强制。 适用于对实时性要求不高的报表或决策支持系统,可最大化查询性能,但有返回旧数据的风险。
  • 查看当前值:

    复制代码
    SHOW PARAMETER QUERY_REWRITE_INTEGRITY;
  • 修改设置:

    复制代码
    -- 会话级别修改
    ALTER SESSION SET QUERY_REWRITE_INTEGRITY = TRUSTED;
    
    -- 系统级别修改(影响所有新会话)
    ALTER SYSTEM SET QUERY_REWRITE_INTEGRITY = TRUSTED;

二、物化视图用途

2.1 性能优化

物化视图最核心的用途是提前将复杂的、涉及大量物理读的查询结果计算出来并保存,后续查询直接读取预计算结果,避免重复执行昂贵的聚合、连接操作。

典型场景:

  • 数据仓库中的汇总报表(如按月/按区域的销售额统计)
  • 频繁执行的复杂 JOIN 查询
  • 包含聚合函数(SUM、COUNT、AVG 等)的分析查询
  • 跨多张大表的关联查询

性能提升原理:

复制代码
原始查询:扫描 1000 万行 → 聚合计算 → 返回 100 行结果(耗时 5 秒)
物化视图:直接读取已存储的 100 行结果(耗时 0.01 秒)

2.2 数据迁移与跨库同步

物化视图可以通过 DB Link 从远程数据库拉取数据,实现不同服务器之间的数据同步。在目标库创建物化视图时,使用 ON PREBUILT TABLE 子句指定一个已存在的表作为存储容器。

ON PREBUILT TABLE 的本质是告诉 Oracle:"我已经有一张现成的表了,它的数据就是我想让物化视图展示的内容,请直接把它当作物化视图来管理。"

典型场景:

  • 数据仓库环境:在数据仓库中,构建基础表可能是一个耗时很长的ETL过程。使用此选项可以将已经构建好的表快速注册为物化视图。
  • 数据迁移与同步:在通过DBLINK进行数据迁移时,可以先在目标库创建好与源库结构一致的表并传输数据,然后再将其注册为物化视图,建立同步关系。
  • 从生产库同步汇总数据到报表库
  • 跨地域数据库之间的数据分发
  • 读写分离架构中的数据同步

工作原理:

复制代码
┌─────────────────┐     DB Link      ┌─────────────────┐
│   源数据库       │  ──────────────> │   目标数据库      │
│  (生产库)        │                  │  (报表库)        │
│                 │                  │                 │
│  sales 表       │                  │  mv_sales 表     │
│  + MV Log       │                  │  (ON PREBUILT   │
│                 │                  │   TABLE)        │
└─────────────────┘                  └─────────────────┘

ON PREBUILT TABLE 的优势:

  • 物化视图的数据存储在预先创建好的表中,表的名称可以与物化视图名称不同
  • 删除物化视图时,底层表不会被删除(普通物化视图删除时表会一起删除),对于使用 ON PREBUILT TABLE 创建的物化视图,执行 DROP MATERIALIZED VIEW 时,默认会保留底层的容器表。这意味着你可以暂时移除物化视图的定义,而数据依然安全地保存在表中。
  • 可以在已有表上添加索引、分区等结构后再创建物化视图

三、物化视图参数

3.1 权限要求

创建物化视图需要以下权限:

sql 复制代码
-- 基本权限
GRANT CREATE MATERIALIZED VIEW TO <用户名>;

-- 如果要在其他用户的模式下创建
GRANT CREATE ANY MATERIALIZED VIEW TO <用户名>;

-- 如果物化视图引用了其他用户的表
GRANT SELECT ON <表名> TO <用户名>;

-- 如果需要查询重写
GRANT QUERY REWRITE TO <用户名>;  -- 或设置系统参数

3.2 参数关键字详解

BUILD 子句 ------ 创建时机
关键字 说明
BUILD IMMEDIATE 默认选项。创建时立即执行查询并填充数据
BUILD DEFERRED 非默认选项。只创建物化视图的结构定义,不填充数据,需后续手动刷新
sql 复制代码
-- 立即填充数据(默认)
CREATE MATERIALIZED VIEW mv_test
  BUILD IMMEDIATE
  AS SELECT ...;

-- 延迟创建,只建结构不填数据
CREATE MATERIALIZED VIEW mv_test
  BUILD DEFERRED
  AS SELECT ...;
REFRESH 子句 ------ 刷新方式(方法)
关键字 说明
FAST 快速刷新(增量刷新)。仅应用物化视图日志中的增量变更,要求基表上存在 MV Log
COMPLETE 完全刷新。重新执行物化视图的完整查询,覆盖所有数据
FORCE 强制刷新(默认)。优先尝试 FAST,若条件不满足则自动退化为 COMPLETE
NEVER 永不刷新。物化视图创建后不再更新,适用于静态数据快照
sql 复制代码
-- 快速刷新
CREATE MATERIALIZED VIEW mv_test REFRESH FAST AS SELECT ...;

-- 完全刷新
CREATE MATERIALIZED VIEW mv_test REFRESH COMPLETE AS SELECT ...;

-- 强制刷新(默认)
CREATE MATERIALIZED VIEW mv_test REFRESH FORCE AS SELECT ...;

-- 永不刷新
CREATE MATERIALIZED VIEW mv_test REFRESH NEVER AS SELECT ...;
REFRESH 子句 ------ 刷新时机(模式)
关键字 说明
ON DEMAND 按需刷新(默认) 。由用户手动调用 DBMS_MVIEW.REFRESH 或通过 Job 定时调度
ON COMMIT 提交时刷新。基表事务提交时自动触发快速刷新,物化视图始终与基表同步
ON STATEMENT 语句级刷新。基表上每条 DML 语句执行后立即刷新(12c 引入)
sql 复制代码
-- 按需刷新(需手动或定时触发)
CREATE MATERIALIZED VIEW mv_test
  REFRESH FAST ON DEMAND
  AS SELECT ...;

-- 提交时自动刷新
CREATE MATERIALIZED VIEW mv_test
  REFRESH FAST ON COMMIT
  AS SELECT ...;
ENABLE QUERY REWRITE ------ 查询重写

启用后,当用户执行的查询与物化视图的定义查询匹配(或可推导)时,Oracle 优化器会自动将查询重定向到物化视图,无需修改应用 SQL。

sql 复制代码
CREATE MATERIALIZED VIEW mv_test
  ENABLE QUERY REWRITE
  AS SELECT ...;

需要配合系统参数:

sql 复制代码
-- 确保查询重写功能开启
ALTER SYSTEM SET query_rewrite_enabled = TRUE;
ALTER SYSTEM SET query_rewrite_integrity = ENFORCED;  -- 默认值

3.3 完整创建语法总览

sql 复制代码
CREATE MATERIALIZED VIEW [模式名.]物化视图名
  [BUILD {IMMEDIATE | DEFERRED}]                    -- 创建时机
  [REFRESH {FAST | COMPLETE | FORCE}                -- 刷新方式
   {ON DEMAND | ON COMMIT | ON STATEMENT}]          -- 刷新时机
  [ENABLE QUERY REWRITE]                            -- 启用查询重写
  [ON PREBUILT TABLE]                               -- 使用已有表
  AS <SELECT 查询语句>;

各参数含义如下:

参数 说明
BUILD IMMEDIATE 默认。创建时立即填充数据
BUILD DEFERRED 创建时不填充数据,需手动执行首次完全刷新
REFRESH FAST 仅使用物化视图日志进行增量刷新
REFRESH COMPLETE 完全重新执行查询
REFRESH FORCE 默认。优先 FAST,失败则 COMPLETE
REFRESH NEVER 从不自动刷新,只能手动
ON DEMAND 默认。手动触发刷新
ON COMMIT 基表提交时自动刷新
ENABLE QUERY REWRITE 允许优化器将查询重写为使用该物化视图
ON PREBUILT TABLE 将现有表注册为物化视图

3.4 删除物化视图流程

复制代码
清除痕迹:(删除顺序反过来)
drop materialized view sales_mv;  --先删除物化视图
drop materialized view log on times;  --在删除物化视图日志
drop materialized view log on products;
drop materialized view log on sales;

四、物化视图刷新

4.1 手动刷新

DBMS_MVIEW.REFRESH ------ 刷新指定物化视图
sql 复制代码
-- 快速刷新单个物化视图
EXEC DBMS_MVIEW.REFRESH('MV_SALES_SUMMARY', 'F');

-- 完全刷新
EXEC DBMS_MVIEW.REFRESH('MV_SALES_SUMMARY', 'C');

-- 强制刷新(优先 FAST,不行就 COMPLETE)
EXEC DBMS_MVIEW.REFRESH('MV_SALES_SUMMARY', '?');

-- 刷新多个物化视图(逗号分隔)
EXEC DBMS_MVIEW.REFRESH('MV1,MV2,MV3', 'F');

DBMS_MVIEW.REFRESH_ALL_MVIEWS ------ 刷新所有物化视图

DBMS_MVIEW.REFRESH_DEPENDENT ------ 刷新依赖指定表的所有物化视图

4.2 定时刷新(通过 Job 调度)
sql 复制代码
-- 创建物化视图时指定定时刷新计划
CREATE MATERIALIZED VIEW mv_daily_sales
  REFRESH FAST
  START WITH SYSDATE                              -- 立即开始
  NEXT SYSDATE + INTERVAL '1' HOUR                -- 每小时刷新一次
  ENABLE QUERY REWRITE
  AS SELECT ...;

-- 修改已有物化视图的刷新计划
ALTER MATERIALIZED VIEW mv_daily_sales
  REFRESH FAST
  START WITH SYSDATE
  NEXT TRUNC(SYSDATE + 1) + 6/24;                 -- 每天早上 6 点刷新

五、物化视图相关数据字典视图

视图名 说明
DBA_MVIEWS / USER_MVIEWS 查看所有物化视图的定义、刷新模式、刷新方式、状态等
DBA_MVIEW_LOGS / USER_MVIEW_LOGS 查看所有物化视图日志的信息
DBA_TUNE_MVIEW / USER_TUNE_MVIEW 查看 DBMS_ADVISOR.TUNE_MVIEW 生成的优化建议
DBA_MVIEW_DETAIL_RELATIONS 查看物化视图与基表的依赖关系
DBA_MVIEW_DETAIL_PARTITION 查看物化视图分区级别的新鲜度(PCT 相关)
sql 复制代码
-- 查看物化视图状态
SET LINESIZE 200 PAGESIZE 999
COL MVIEW_NAME FORMAT A20
SELECT mview_name,
       refresh_mode,       -- 刷新模式:COMMIT / DEMAND
       refresh_method,     -- 刷新方式:FAST / COMPLETE / FORCE
       staleness,          -- 新鲜度:FRESH / STALE / NEEDS_COMPILE
       last_refresh_type,  -- 上次刷新类型
       last_refresh_date   -- 上次刷新时间
FROM   dba_mviews
WHERE  mview_name = 'MV_SALES_SUMMARY';

-- 查看物化视图日志
SELECT master, log_table, rowids, primary_key, sequence, include_new_values
FROM   dba_mview_logs
WHERE  master = 'SALES';

MASTER                         LOG_TABLE                      ROW PRI SEQ INC
------------------------------ ------------------------------ --- --- --- ---
EMP                            MLOG$_EMP                      NO  YES NO  YES

六、使用 DBMS_ADVISOR.TUNE_MVIEW 创建和优化物化视图(推荐)

从 Oracle 10g 开始,DBMS_ADVISOR.TUNE_MVIEW 过程可以自动分析你的物化视图创建语句,并给出最优建议(包括是否需要创建物化视图日志、如何调整 SQL 等),极大降低了人工分析的出错概率。

  • 需要创建哪些物化视图日志
  • 物化视图查询需要如何改写(如将 DISTINCT 改为 GROUP BY + COUNT(*))
  • 完整的实施脚本(创建物化视图、日志、刷新等)和回退脚本

强烈推荐使用此工具,因为手动编写物化视图时很容易遗漏快速刷新的必要条件,而这个工具能自动帮你补全。

6.1 使用步骤

sql 复制代码
-- sh用户授权
alter user sh account unlock;
alter user sh identified by sh;
grant CREATE MATERIALIZED VIEW to sh;

-- 步骤 1:定义变量接收任务名
VARIABLE task_name VARCHAR2(30);

-- 步骤 2:调用 TUNE_MVIEW,传入你的物化视图创建语句
BEGIN
  DBMS_ADVISOR.TUNE_MVIEW(
    :task_name,
    mv_create_stmt => '
      create materialized view sales_mv  REFRESH fast enable query rewrite  AS select t.calendar_year, p.prod_id, sum(s.amount_sold) as sum_sales 
from sh.times t, sh.products p, sh.sales s 
where t.time_id = s.time_id 
and p.prod_id = s.prod_id 
group by t.calendar_year, p.prod_id'
  );
END;
/

-- 步骤 3:查看任务名
PRINT task_name;

-- 步骤 4:查看优化建议(注意先设置 LONG 以避免 CLOB 被截断)
SET LONG 99999;
set linesize 200 pagesize 999
SELECT script_type, statement
FROM   dba_tune_mview
WHERE  task_name = :task_name
ORDER BY script_type, action_id;

6.2 输出示例

TUNE_MVIEW 会输出三类脚本:

SCRIPT_TYPE 说明
IMPLEMENTATION 实施脚本,按顺序执行即可
UNDO 回退脚本,用于撤销操作
CREATE MATERIALIZED VIEW 优化后的物化视图创建语句

典型输出:

sql 复制代码
SCRIPT_TYPE    STATEMENT
-------------- --------------------------------------------------------------------------------
-- IMPLEMENTATION: 需要创建的物化视图日志
IMPLEMENTATION CREATE MATERIALIZED VIEW LOG ON "SH"."SALES" WITH ROWID, SEQUENCE ("PROD_ID","TI
               ME_ID","AMOUNT_SOLD")  INCLUDING NEW VALUES
IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE ON "SH"."SALES" ADD ROWID, SEQUENCE ("PROD_ID"
               ,"TIME_ID","AMOUNT_SOLD")  INCLUDING NEW VALUES
IMPLEMENTATION CREATE MATERIALIZED VIEW LOG ON "SH"."PRODUCTS" WITH ROWID, SEQUENCE ("PROD_ID")
                 INCLUDING NEW VALUES
IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE ON "SH"."PRODUCTS" ADD ROWID, SEQUENCE ("PROD_
               ID")  INCLUDING NEW VALUES
IMPLEMENTATION CREATE MATERIALIZED VIEW LOG ON "SH"."TIMES" WITH ROWID, SEQUENCE ("TIME_ID","CA
               LENDAR_YEAR")  INCLUDING NEW VALUES
IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE ON "SH"."TIMES" ADD ROWID, SEQUENCE ("TIME_ID"
               ,"CALENDAR_YEAR")  INCLUDING NEW VALUES

-- IMPLEMENTATION: 优化后的创建物化视图的语句
IMPLEMENTATION CREATE MATERIALIZED VIEW SH.SALES_MV   REFRESH FAST WITH ROWID ENABLE QUERY REWR
               ITE AS SELECT SH.TIMES.CALENDAR_YEAR C1, SH.SALES.PROD_ID C2, SUM("SH"."SALES"."
               AMOUNT_SOLD") M1, COUNT("SH"."SALES"."AMOUNT_SOLD") M2, COUNT(*) M3 FROM SH.TIME
               S, SH.PRODUCTS, SH.SALES WHERE SH.SALES.TIME_ID = SH.TIMES.TIME_ID AND SH.SALES.
               PROD_ID = SH.PRODUCTS.PROD_ID GROUP BY SH.TIMES.CALENDAR_YEAR, SH.SALES.PROD_ID

-- UNDO: 回退脚本
UNDO           DROP MATERIALIZED VIEW SH.SALES_MV

6.3 DBMS_ADVISOR将所有语句存放在一个目录文件的方法

sql 复制代码
-- sh用户授权
alter user sh account unlock;
alter user sh identified by sh;
grant CREATE MATERIALIZED VIEW to sh;
grant advisor to sh;

-- 创建目录
mkdir /oradata/expdh  --操作系统层面创建

-- 数据库中创建directory,用于保存
create directory expdh as '/oradata/expdh';
grant all on directory expdh to sh;

-- 调用 TUNE_MVIEW,传入你的物化视图创建语句
DECLARE
  v_tname VARCHAR2(30);
BEGIN
  v_tname := 'mview_task1';
  DBMS_ADVISOR.TUNE_MVIEW(
      task_name      => v_tname
  ,   mv_create_stmt => 
       'create materialized view sales_mv  REFRESH fast enable query rewrite  AS select t.calendar_year, p.prod_id, sum(s.amount_sold) as sum_sales 
from sh.times t, sh.products p, sh.sales s 
where t.time_id = s.time_id 
and p.prod_id = s.prod_id 
group by t.calendar_year, p.prod_id');
END;
/


-- 直接将所有语句存放在一个目录文件中:
BEGIN 
  DBMS_ADVISOR.CREATE_FILE(
      buffer     => DBMS_ADVISOR.GET_TASK_SCRIPT( task_name => 'TASK_15')
  ,   location   => 'EXPDH'
  ,   filename   => 'mview_create.sql' );
END;
/


-- 输出样例
[oracle@host51 expdh]$ ll /oradata/expdh
-rw-r--r-- 1 oracle oinstall 1353 Aug 18 15:29 mview_create.sql

[oracle@host51 expdh]$ cat /oradata/expdh/mview_create.sql 
Rem  SQL Access Advisor: Version 11.2.0.4.0 - Production
Rem  
Rem  Username:        SH
Rem  Task:            TASK_15
Rem  Execution date:   
Rem  

CREATE MATERIALIZED VIEW LOG ON
    "SH"."SALES"
    WITH ROWID, SEQUENCE("PROD_ID","TIME_ID","AMOUNT_SOLD")
    INCLUDING NEW VALUES;

ALTER MATERIALIZED VIEW LOG FORCE ON
    "SH"."SALES"
    ADD ROWID, SEQUENCE("PROD_ID","TIME_ID","AMOUNT_SOLD")
    INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW LOG ON
    "SH"."PRODUCTS"
    WITH ROWID, SEQUENCE("PROD_ID")
    INCLUDING NEW VALUES;

ALTER MATERIALIZED VIEW LOG FORCE ON
    "SH"."PRODUCTS"
    ADD ROWID, SEQUENCE("PROD_ID")
    INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW LOG ON
    "SH"."TIMES"
    WITH ROWID, SEQUENCE("TIME_ID","CALENDAR_YEAR")
    INCLUDING NEW VALUES;

ALTER MATERIALIZED VIEW LOG FORCE ON
    "SH"."TIMES"
    ADD ROWID, SEQUENCE("TIME_ID","CALENDAR_YEAR")
    INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW SH.SALES_MV
    REFRESH FAST WITH ROWID
    ENABLE QUERY REWRITE
    AS SELECT SH.TIMES.CALENDAR_YEAR C1, SH.SALES.PROD_ID C2, SUM("SH"."SALES"."AMOUNT_SOLD")
       M1, COUNT("SH"."SALES"."AMOUNT_SOLD") M2, COUNT(*) M3 FROM SH.TIMES, SH.PRODUCTS,
       SH.SALES WHERE SH.SALES.TIME_ID = SH.TIMES.TIME_ID AND SH.SALES.PROD_ID
       = SH.PRODUCTS.PROD_ID GROUP BY SH.TIMES.CALENDAR_YEAR, SH.SALES.PROD_ID;

七、12c 实时物化视图(Real-Time Materialized View)

7.1 背景与问题

在 Oracle 12c 之前,物化视图面临一个两难选择:

方案 优点 缺点
REFRESH ON COMMIT 数据始终最新 每次提交都有刷新开销,拖慢事务
REFRESH ON DEMAND 不影响事务性能 数据可能过时(STALE),查询回退到全表扫描

当物化视图变为 STALE 状态时,优化器会放弃使用物化视图,查询回退到扫描基表,性能急剧下降。

7.2 实时物化视图的解决方案

Oracle 12.2 引入了实时物化视图(Real-Time Materialized View),核心思想是:

即使物化视图是 STALE 的,查询时也能自动将物化视图日志中的增量变更与物化视图中的旧数据合并,计算出正确的最新结果。

复制代码
┌──────────────┐                    ┌─────────────────┐
│   基表        │  DML 变更后        │  MV Log         │
│              │ ────────────────>  │  (记录增量变更)   │
└──────────────┘                    └────────┬────────┘
                                             │
                                             │ 查询时自动合并
                                             ▼
                              ┌──────────────────────────┐
                              │    实时物化视图 (MV)       │
                              │  ┌────────────────────┐  │
                              │  │ 已存储的旧数据       │  │
                              │  │ + MV Log 中的增量    │  │
                              │  │ = 最新正确结果       │  │
                              │  └────────────────────┘  │
                              └──────────────────────────┘

7.3 创建实时物化视图

sql 复制代码
-- 前提:基表上必须存在物化视图日志
CREATE MATERIALIZED VIEW LOG ON sales
  WITH ROWID, SEQUENCE(channel_id, amount_sold)
  INCLUDING NEW VALUES;

-- 创建实时物化视图
CREATE MATERIALIZED VIEW mv_sales_summary
  REFRESH FAST ON DEMAND                    -- 必须是 ON DEMAND(不能是 ON COMMIT)
  ENABLE QUERY REWRITE                      -- 启用查询重写
  ENABLE ON QUERY COMPUTATION               -- 关键:启用实时计算
  AS
  SELECT channel_id,
         SUM(amount_sold)  AS sum_amount,
         COUNT(amount_sold) AS cnt_amount,
         COUNT(*)           AS cnt_star
  FROM   sales
  GROUP BY channel_id;

7.4 工作机制

  1. 物化视图是 FRESH 时:直接读取物化视图数据,性能最优
  2. 物化视图是 STALE 时 :优化器自动将物化视图数据与 MV Log 中的增量变更通过 UNION-ALL + HASH JOIN FULL OUTER 合并,计算出正确结果
  3. 优化器会进行代价评估:如果 MV Log 中的变更量太大(合并代价高于直接查基表),优化器会放弃使用实时物化视图,回退到全表扫描

7.5 性能对比

sql 复制代码
-- 假设基表 SALES 有 100 万行

-- 无物化视图:全表扫描 100 万行
SELECT channel_id, SUM(amount_sold) FROM sales GROUP BY channel_id;
-- 耗时:3.47 秒,逻辑读:270

-- 物化视图 FRESH 时:直接读取 5 行结果
-- 耗时:0.07 秒,逻辑读:9

-- 物化视图 STALE + 实时计算:读取 MV(5行) + MV Log(少量变更)
-- 耗时:0.12 秒,逻辑读:25
-- 虽然比 FRESH 慢,但远快于全表扫描!

7.6 使用建议

  • 定期刷新 :实时物化视图不等于不需要刷新。应定期执行 DBMS_MVIEW.REFRESH 清理 MV Log,保持增量变更量较小
  • 适合场景:读多写少、查询频率高、对数据新鲜度要求较高但不能承受 ON COMMIT 开销的场景
  • 注意事项:如果 MV Log 积压过多变更未刷新,实时计算的代价可能超过直接查基表,优化器会自动放弃使用

7.7 将已有物化视图转换为实时物化视图

sql 复制代码
-- 先确保物化视图支持 FAST 刷新且为 ON DEMAND 模式
ALTER MATERIALIZED VIEW mv_sales_summary
  REFRESH FAST ON DEMAND;

-- 启用实时计算
ALTER MATERIALIZED VIEW mv_sales_summary
  ENABLE ON QUERY COMPUTATION;

八、完整案例

订单表 orders(约 100 万行)和客户表 customers(约 50 万行),查询"各城市每月的订单汇总统计",通过物化视图将查询时间降低到毫秒级

步骤一:准备环境

sql 复制代码
-- 创建客户表
CREATE TABLE customers (
    cust_id     NUMBER PRIMARY KEY,
    cust_name   VARCHAR2(100),
    city        VARCHAR2(50),
    province    VARCHAR2(50)
);

-- 创建订单表
CREATE TABLE orders (
    order_id    NUMBER PRIMARY KEY,
    cust_id     NUMBER,
    order_date  DATE,
    amount      NUMBER(12,2),
    status      VARCHAR2(20)
);

-- 插入模拟数据
INSERT INTO customers
SELECT ROWNUM,
       'Customer_' || ROWNUM,
       DECODE(MOD(ROWNUM,5), 0,'北京', 1,'上海', 2,'广州', 3,'深圳', 4,'杭州'),
       DECODE(MOD(ROWNUM,5), 0,'华北', 1,'华东', 2,'华南', 3,'华南', 4,'华东')
FROM   DUAL CONNECT BY LEVEL <= 500000;

INSERT INTO orders
SELECT ROWNUM,
       MOD(ROWNUM, 500000) + 1,
       TO_DATE('2025-01-01','YYYY-MM-DD') + MOD(ROWNUM, 365),
       ROUND(DBMS_RANDOM.VALUE(10, 10000), 2),
       DECODE(MOD(ROWNUM,3), 0,'已完成', 1,'处理中', 2,'已取消')
FROM   DUAL CONNECT BY LEVEL <= 1000000;

COMMIT;

-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'CUSTOMERS');
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'ORDERS');

步骤二:验证原始查询性能

sql 复制代码
SET TIMING ON;
set autotrace on
SELECT c.city,TRUNC(o.order_date, 'MM') AS order_month,COUNT(*) AS cnt
  FROM   orders o, customers c
 WHERE  o.cust_id = c.cust_id
 GROUP BY c.city, TRUNC(o.order_date, 'MM');

-- 逻辑读约 25412
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     25412  consistent gets
          0  physical reads

步骤三:使用 TUNE_MVIEW 获取优化建议

sql 复制代码
VARIABLE task_name VARCHAR2(100);

BEGIN
  DBMS_ADVISOR.TUNE_MVIEW(
    :task_name,
    mv_create_stmt => '
      CREATE MATERIALIZED VIEW mv_test
      REFRESH FAST
      AS
      SELECT c.city,
             TRUNC(o.order_date, ''MM'') AS order_month,
             COUNT(*) AS cnt
      FROM   orders o, customers c
      WHERE  o.cust_id = c.cust_id
      GROUP BY c.city, TRUNC(o.order_date, ''MM'')'
  );
END;
/

SET LONG 99999;
set linesize 200 pagesize 999
SELECT script_type, statement
FROM   user_tune_mview
WHERE  task_name = :task_name
ORDER BY script_type, action_id;

步骤四:根据建议创建物化视图日志

sql 复制代码
-- TUNE_MVIEW 会建议在基表上创建物化视图日志
-- 在 orders 表上创建
CREATE MATERIALIZED VIEW LOG ON
    "USER1"."ORDERS"
    WITH ROWID, SEQUENCE("CUST_ID","ORDER_DATE")
    INCLUDING NEW VALUES;

-- 在 customers 表上创建
CREATE MATERIALIZED VIEW LOG ON
    "USER1"."CUSTOMERS"
    WITH ROWID, SEQUENCE("CUST_ID","CITY")
    INCLUDING NEW VALUES;

步骤五:创建物化视图

sql 复制代码
-- 根据 TUNE_MVIEW 的建议,创建优化后的物化视图
CREATE MATERIALIZED VIEW USER1.MV_TEST
    REFRESH FAST WITH ROWID
    DISABLE QUERY REWRITE
    AS 
SELECT USER1.ORDERS.ORDER_DATE C1, USER1.CUSTOMERS.CITY C2, COUNT(*) M1 
  FROM USER1.ORDERS, USER1.CUSTOMERS 
 WHERE USER1.CUSTOMERS.CUST_ID = USER1.ORDERS.CUST_ID
 GROUP BY USER1.ORDERS.ORDER_DATE, USER1.CUSTOMERS.CITY;

步骤六:创建索引并验证查询重写

sql 复制代码
-- 收集物化视图统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MV_TEST');

-- 验证查询是否被重写
SET TIMING ON;
set autotrace on
SELECT c.city,TRUNC(o.order_date, 'MM') AS order_month,COUNT(*) AS cnt
  FROM   orders o, customers c
 WHERE  o.cust_id = c.cust_id
 GROUP BY c.city, TRUNC(o.order_date, 'MM');
set autotrace off

Plan hash value: 2137789089
---------------------------------------------------------------------------------------------
| Id  | Operation                         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |         |  8168 | 16336 |    29   (0)| 00:00:01 |
|   1 |  COLLECTION ITERATOR PICKLER FETCH| DISPLAY |  8168 | 16336 |    29   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

-- 逻辑读约 189
Statistics
----------------------------------------------------------
         28  recursive calls
         12  db block gets
        189  consistent gets

步骤七:验证增量刷新

sql 复制代码
-- 查看物化视图状态
set linesize 200 pagesize 999
col mview_name format a20
col staleness format a20
col time format a25
SELECT mview_name, staleness, last_refresh_type, to_char(last_refresh_date,'yyyymmdd hh24:mi:ss') time
FROM   user_mviews
WHERE  mview_name = 'MV_TEST';

MVIEW_NAME           STALENESS            LAST_REFRESH_TYPE        TIME
-------------------- -------------------- ------------------------ -----------------------
MV_TEST              FRESH                COMPLETE                 20260818 16:00:33

-- 模拟基表数据变更
INSERT INTO orders VALUES (5000001, 100, TO_DATE('2024-06-15','YYYY-MM-DD'), 5000, '已完成');
INSERT INTO orders VALUES (5000002, 200, TO_DATE('2024-06-20','YYYY-MM-DD'), 8000, '已完成');
COMMIT;

-- 此时物化视图变为 STALE
SELECT mview_name, staleness FROM user_mviews
WHERE mview_name = 'MV_TEST';
-- STALENESS: NEEDS_COMPILE
MVIEW_NAME           STALENESS
-------------------- --------------------
MV_TEST              NEEDS_COMPILE


-- 执行快速刷新
EXEC DBMS_MVIEW.REFRESH('MV_TEST', 'F');

-- 刷新后恢复 FRESH
set linesize 200 pagesize 999
col mview_name format a20
col staleness format a20
col time format a25
SELECT mview_name, staleness, last_refresh_type, to_char(last_refresh_date,'yyyymmdd hh24:mi:ss') time
FROM   user_mviews
WHERE  mview_name = 'MV_TEST';
-- STALENESS: FRESH, LAST_REFRESH_TYPE: FAST

MVIEW_NAME           STALENESS            LAST_REFRESH_TYPE        TIME
-------------------- -------------------- ------------------------ -------------------------
MV_TEST              FRESH                FAST                     20260818 16:06:19

步骤八:设置定时刷新 Job

sql 复制代码
-- 每小时自动刷新一次
ALTER MATERIALIZED VIEW MV_TEST
  REFRESH FAST
  START WITH SYSDATE
  NEXT SYSDATE + 1/24;    -- 每 1 小时

步骤九:清理顾问任务

sql 复制代码
EXEC DBMS_ADVISOR.DELETE_TASK(:task_name);

九、常见问题与注意事项

9.1 快速刷新失败退化为完全刷新

原因 :物化视图日志缺少必要参数(如未指定 SEQUENCE 或 INCLUDING NEW VALUES),或物化视图查询包含不支持快速刷新的语法。

排查方法:

sql 复制代码
-- EXPLAIN_MVIEW 过程会将分析结果写入 MV_CAPABILITIES_TABLE 这张表里。如果你的数据库里还没有这张表,需要先手动创建
@$ORACLE_HOME/rdbms/admin/utlxmv.sql

-- 使用 EXPLAIN_MVIEW 检查快速刷新能力
EXEC DBMS_MVIEW.EXPLAIN_MVIEW('MV_TEST');

SELECT capability_name, possible, msgtxt
FROM   mv_capabilities_table
WHERE  capability_name LIKE 'REFRESH_FAST%';

输出类似如下:
REFRESH_FAST                                                                               Y
REFRESH_FAST_AFTER_INSERT                                                                  Y
REFRESH_FAST_AFTER_ONETAB_DML                                                              Y
REFRESH_FAST_AFTER_ANY_DML                                                                 Y
REFRESH_FAST_PCT                                                                           N
PCT is not possible on any of the detail tables in the materialized view

9.2 查询重写未生效

排查清单:

  1. query_rewrite_enabled 参数是否为 TRUE
  2. 物化视图是否启用了 ENABLE QUERY REWRITE
  3. 物化视图是否为 FRESH 状态(非 STALE)
  4. 查询语句是否与物化视图定义匹配

9.3 ON COMMIT 刷新的性能影响

REFRESH FAST ON COMMIT 会在每次事务提交时触发刷新,对写入密集的场景可能造成明显的提交延迟。建议:

  • 在测试环境充分评估提交延迟
  • 对于写多读少的场景,优先使用 ON DEMAND + 定时 Job
  • 考虑使用 12c 的实时物化视图替代 ON COMMIT

9.4 物化视图日志的空间管理

物化视图日志会持续增长,如果不及时刷新,日志表会变得非常大。建议:

  • 定期监控 MV Log 的大小
  • 确保刷新频率足够高,及时清理已消费的日志记录
  • 对于多个物化视图共享同一个 MV Log 的情况,刷新频率取决于最慢的那个物化视图

DBMS_MVIEW.PURGE_LOG此过程用于清理指定基表的物化视图日志,可以保留最近N次刷新所需的条目。

清理所有已消费的日志,彻底清理所有不再需要的日志条目。

复制代码
BEGIN
  -- 指定一个非常大的数字,确保所有日志都被清理
  DBMS_MVIEW.PURGE_LOG(
    master => 'SCHEMA_NAME.TABLE_NAME', 
    num    => 9999
  );
END;
/

注意 :此操作执行后,所有依赖该日志的物化视图下次都需要完全刷新(Complete Refresh)。

按时间窗口保留日志,只清理指定天数之前的日志,例如,仅保留最近7天的日志。

复制代码
BEGIN
  -- 此处以天数为例,具体参数请参考官方文档
  DBMS_MVIEW.PURGE_LOG(
    master => 'SCHEMA_NAME.TABLE_NAME',
    num    => 7
  );
END;
/

DBMS_MVIEW.PURGE_MVIEW_FROM_LOG此过程用于清理与特定、已注销的物化视图相关的日志条目。通常用于处理那些已被删除但未正确注销,导致日志无法自动清理的"孤立"物化视图。

相关推荐
RuoyiOffice1 小时前
达梦 DM8、openGauss、人大金仓怎么选?RuoYi Office 国产数据库适配与验收指南
java·数据库·spring boot·架构·信创
星恒讯工业路由器2 小时前
工业网络设备选型:导轨式、机架式与车载式的部署差异
开发语言·网络·数据库·php·工业路由器·导轨式·车载式
wespaul4 小时前
VeapAI 自然语言操作实战|第 05 篇:向量与召回——从知识条目到可检索资产
大数据·数据库·人工智能·milvus
一只鹿鹿鹿10 小时前
政务外网整体设计方案(PPT)
大数据·数据库·web安全·系统安全·流程图·政务
2603_9651481110 小时前
社区与学习资源:从入门到精通的API选品学习路径
数据库·人工智能·python·学习·数据挖掘
AI砖家10 小时前
安全进入 Agent Loop:2027 年 Coding Agent 的决胜点
数据库·安全·codex·vibecoding
wangqiaowq10 小时前
SQL server C盘空间占满的处理
数据库·sql
文人sec10 小时前
SQL基础入门笔记 | 从零基础到掌握核心查询语句
数据库·笔记·sql
数字新视界10 小时前
机房动环监控提升运维效率
大数据·服务器·数据库·动环监控系统·动力与环境监控系统