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 都已消费的日志记录
其工作原理如下:
- 当基表发生 DML 操作时,一个内部触发器会被激活。
- 该触发器将受影响行的信息(如 ROWID、主键值或变更列的新旧值)记录到物化视图日志中。
- 当物化视图执行快速刷新时,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 工作机制
- 物化视图是 FRESH 时:直接读取物化视图数据,性能最优
- 物化视图是 STALE 时 :优化器自动将物化视图数据与 MV Log 中的增量变更通过
UNION-ALL + HASH JOIN FULL OUTER合并,计算出正确结果 - 优化器会进行代价评估:如果 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 查询重写未生效
排查清单:
query_rewrite_enabled参数是否为TRUE- 物化视图是否启用了
ENABLE QUERY REWRITE - 物化视图是否为 FRESH 状态(非 STALE)
- 查询语句是否与物化视图定义匹配
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此过程用于清理与特定、已注销的物化视图相关的日志条目。通常用于处理那些已被删除但未正确注销,导致日志无法自动清理的"孤立"物化视图。