物化视图是一种特殊的视图,普通视图不会对数据进行单独存储,而物化视图会定期(取决于物化视图定义的刷新策略)对相应的SQL查询结果数据进行单独存储,由于查询结果是预存下来的,物化视图的查询结果效率远高于普通的视图。对于统计汇总聚合查询缓慢,需要提升查询效率的场景,物化视图是常用手段之一;在表数据量过大,查询历史数据缓慢,且数据不会变动时,除了改造为分区表,也可将需要的历史时段数据创建为物化视图,当访问对应时段的数据时,会匹配相对应的物化视图重写查询直接从物化视图中获取数据(此场景由于数据不会刷新,数据库会将物化视图中的数据标记为"过旧"状态,数据库的查询重写参数控制级别不能太高,否则无法匹配物化视图,此时可降低查询重写控制级别,如:QUERY_REWRITE_INTEGRITY = STALE_TOLERATED),不会从大表中获取数据。
注意:数据查询本身比较缓慢,创建物化视图建议在低峰期进行,创建物化视图时会对基表获取排它锁,并产生额外的I/O资源消耗,耗时过长会对业务产生一定风险。
| 场景 | 痛点 | 物化视图的作用 | 推荐指数 |
|---|---|---|---|
| BI 报表/大屏 | 聚合查询慢,CPU 100% | 预聚合:存结果,查得快 | ⭐⭐⭐⭐⭐ |
| 多表关联查询 | Join 太多,IO 消耗大 | 预连接:存宽表,免 Join | ⭐⭐⭐⭐⭐ |
| 数据仓库 | ETL 脚本复杂,维护难 | 自动化:替代调度脚本 | ⭐⭐⭐⭐ |
| 异地容灾 | 跨网查询慢,带宽贵 | 数据分发:本地存副本 | ⭐⭐⭐ |
| 高频交易系统 | 实时性要求极高 | 慎用:刷新延迟可能是瓶颈 | ⭐⭐ |
什么时候不要用:
数据实时性要求极高: 物化视图(特别是异步刷新)有延迟。如果你需要看到毫秒级的最新数据,请直接查基表。
基表频繁更新: 如果基表每秒都在发生大量 UPDATE/DELETE,维护物化视图日志和刷新的开销可能会超过查询带来的收益,甚至拖垮数据库(正如你之前遇到的创建日志耗时问题)。
**查询模式不固定:**如果你的查询全是 Ad-Hoc(即席查询),没有重复的模式,物化视图无法命中,只会浪费存储空间。
本文以Oracle19c为例,进行简单介绍。
核心语法公式
你可以把创建语句看作是由以下几个模块组成的:
sql
CREATE MATERIALIZED VIEW 视图名称
[BUILD IMMEDIATE | BUILD DEFERRED] -- 1. 何时生成数据
[REFRESH [FAST | COMPLETE | FORCE]] -- 2. 如何刷新数据
[ON [COMMIT | DEMAND]] -- 3. 何时触发刷新
[START WITH 时间 NEXT 时间间隔] -- 4. 定时刷新设置(可选))
[ENABLE QUERY REWRITE] -- 5. 是否允许查询重写(可选,指定此项,查询会匹配该物化视图进行查询重写)
AS
SELECT ...; -- 6. 定义数据的查询语句
| 参数模块 | 选项 | 含义与区别 |
|---|---|---|
| BUILD | IMMEDIATE (默认) | 创建时立即把数据查出来存好。 |
| DEFERRED | 创建时不存数据(空表),等第一次刷新时才填充。 | |
| REFRESH | FORCE (默认) | 智能模式:优先尝试增量刷新(FAST),不行则全量刷新(COMPLETE)。 |
| FAST | 增量刷新。只同步变化的数据,速度极快,但必须创建物化视图日志。 | |
| COMPLETE | 全量刷新。每次把整个查询重跑一遍覆盖旧数据。 | |
| ON | DEMAND (默认) | 手动刷新。需要DBA或定时任务去触发。 |
| COMMIT | 实时刷新。基表一提交事务,MV自动同步(会有性能损耗)。 |
Syntax
create_materialized_view::=

Description of the illustration create_materialized_view.eps
案例
以下是三种最常见的场景,可以参考。
场景一:最简单的定时全量刷新(适用于报表)
需求 :每天晚上 10 点,统计各部门的工资总额。不需要实时,只要数据准就行。
特点:不需要创建日志,配置简单,适合数据量不是特别巨大的汇总表。
sql
CREATE MATERIALIZED VIEW mv_dept_salary_stat
BUILD IMMEDIATE -- 创建时立即生成数据
REFRESH COMPLETE -- 每次彻底重算
START WITH SYSDATE -- 从现在开始
NEXT TRUNC(SYSDATE + 1) + 22/24 -- 每天 22:00 刷新 (22/24 代表 22小时)
AS
SELECT
department_id,
SUM(salary) as total_salary,
COUNT(*) as emp_count
FROM employees
GROUP BY department_id;
场景二:高性能增量刷新(适用于大数据量同步)
需求 :需要频繁刷新(比如每 5 分钟),或者数据量很大,全量刷新太慢。
前置条件 :必须先给基表创建物化视图日志 ,否则无法使用 FAST 模式。
第一步:创建日志(在基表上操作)
sql
- 假设基表是 orders
-- WITH PRIMARY KEY 表示记录主键变化,INCLUDING NEW VALUES 表示记录更新后的新值
CREATE MATERIALIZED VIEW LOG ON orders
WITH PRIMARY KEY, ROWID
INCLUDING NEW VALUES;
第二步:创建物化视图
sql
CREATE MATERIALIZED VIEW mv_orders_fast
BUILD IMMEDIATE
REFRESH FAST -- 开启增量刷新
ON DEMAND -- 手动或定时触发
START WITH SYSDATE
NEXT SYSDATE + 5/(24*60) -- 每 5 分钟刷新一次
AS
SELECT order_id, customer_id, amount
FROM orders
WHERE status = 'PAID';
场景三:实时同步(适用于数据分发)
需求 :基表一旦插入数据,物化视图必须立刻能查到。
特点 :使用 ON COMMIT,基表提交事务时,Oracle 会顺便把 MV 也刷了。这会增加基表事务的响应时间。
sql
CREATE MATERIALIZED VIEW mv_emp_realtime
BUILD IMMEDIATE
REFRESH FAST -- 必须是 FAST
ON COMMIT -- 基表提交时自动同步
AS
SELECT e.emp_id, e.emp_name, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id;
常用管理命令
创建好之后,你可能需要手动干预或查看状态,以下命令非常有用:
- 手动刷新(立即执行一次):
sql
-- 'C' 代表 Complete (全量), 'F' 代表 Fast (增量)
CALL DBMS_MVIEW.REFRESH('MV_DEPT_SALARY_STAT', 'C');
查看物化视图状态:
sql
SELECT mview_name, refresh_mode, refresh_method, last_refresh_type, last_refresh_date
FROM dba_mviews;
SELECT mview_name, refresh_mode, refresh_method, last_refresh_type, last_refresh_date
FROM user_mviews;
删除物化视图:
sql
DROP MATERIALIZED VIEW mv_dept_salary_stat;
避坑指南
- FAST 刷新失败 :如果你指定了
REFRESH FAST但没有创建MATERIALIZED VIEW LOG,或者查询语句太复杂(如使用了ROWNUM、非主键连接的复杂 Join),刷新会报错或自动降级为 COMPLETE。 - 主键要求 :建议基表必须有主键。如果基表没有主键,创建日志时必须用
WITH ROWID,但这会限制 MV 的功能(例如不支持某些聚合函数的快速刷新)。 - 列名明确 :在
AS SELECT语句中,尽量不要用SELECT *,明确写出列名是更安全的做法。 - 物化视图与基表归属用户不是同一个用户时,需显示声明指定基表,物化视图用户需显示授权(GRANT SELECT ON ... TO ...),直接授权角色权限方式,可能会报错"表或视图不存在";使用快速增量刷新时,物化视图用户也需要具备物化视图日志表的查询权限,物化视图日志表需跟基表同属一个用户。
判断SQL 查询是否利用了物化视图
要判断一段 SQL 查询是否真正利用了物化视图(即发生了查询重写 Query Rewrite ),最准确的方法是查看执行计划。
如果物化视图生效,执行计划中会出现物化视图的名称,或者出现 MAT_VIEW REWRITE 相关的操作。
以下是三种最常用的验证方法,按推荐程度排序:
方法一:使用 EXPLAIN PLAN(最常用)
这是最直接的方法,用于查看优化器"计划"如何执行这条语句。
操作步骤:
-
执行解释计划命令 :
将你的 SQL 语句放入
EXPLAIN PLAN FOR后面执行。sqlEXPLAIN PLAN FOR SELECT department_id, SUM(salary) FROM employees GROUP BY department_id; -
查看执行计划 :
使用
DBMS_XPLAN包显示结果。sqlSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);结果分析:
走了物化视图:
-
在
Operation列中,你会直接看到物化视图的名字 (例如MV_DEPT_SALARY_STAT),而不是原始表名。或者看到类似MAT_VIEW REWRITE的步骤。示例输出片段:
Id Operation Name 1 TABLE ACCESS FULL MV_DEPT_SALARY_STAT
没走物化视图 :
你会看到原始表的名字(例如 EMPLOYEES),以及全表扫描或索引扫描。
方法二:使用 AUTOTRACE(实时执行)
如果你不仅想看计划,还想看实际执行时的统计信息(如逻辑读),可以使用 AUTOTRACE。这需要你的用户有 PLUSTRACE 角色权限。
操作步骤:
sql
SET AUTOTRACE ON EXPLAIN
-- 然后执行你的查询
SELECT department_id, SUM(salary) FROM employees GROUP BY department_id;
结果分析:
查看输出的 Execution Plan 部分,逻辑同上。如果 Name 列显示的是物化视图的名称,说明重写成功。
方法三:查询 V$SQL_PLAN(针对已执行的 SQL)
如果 SQL 已经在生产环境运行过了,你可以从共享池中抓取它的执行计划。
操作步骤:
你需要知道该 SQL 的 SQL_ID。
sql
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('你的SQL_ID', NULL, 'ALL'));
结果分析:
同样观察 OBJECT_NAME 列是否为物化视图的名称。
核心检查:为什么没走物化视图?
当你执行一条 SQL 时,Oracle 内部大概发生了以下步骤:
- 解析器 :"用户查
SELECT dept, SUM(sal) FROM emp。" - 重写器 :"我手里有个物化视图
MV_EMP_DEPT好像存了这个数据。让我看看......嗯,表对得上,列也有,聚合函数也对(语义匹配成功)。" - 优化器 :"好,核算一下。查基表
EMP需要 1000 个 IO,查物化视图MV_EMP_DEPT只要 10 个 IO。成本更低!(COST成本计算成功)" - 检查员 :"视图状态是
FRESH,数据可信。(完整性检查通过)" - 执行器 :"把 SQL 偷偷换成
SELECT * FROM MV_EMP_DEPT,执行!"
一句话总结 :监测靠执行计划 ,原理靠语义匹配 和成本比较
如果你发现执行计划里只有原表,没有物化视图,通常是因为以下原因(按可能性排序):
-
未开启查询重写:
-
会话级开启 :
ALTER SESSION SET QUERY_REWRITE_ENABLED = TRUE; -
系统级开启 :
ALTER SYSTEM SET QUERY_REWRITE_ENABLED = TRUE SCOPE=BOTH``; -
创建时开启 :创建 MV 时使用了
ENABLE QUERY REWRITE子句。sql--查询物化视图状态 SELECT owner, mview_name, staleness, -- 核心状态 compile_state, -- 编译状态 last_refresh_type, -- 上次刷新类型 last_refresh_date -- 上次刷新时间 FROM dba_mviews WHERE mview_name = '你的物化视图名称'; --查询数据库有没有开启查询重写 SELECT name, value FROM v$parameter WHERE name = 'query_rewrite_enabled'; --查询物化视图本身是否允许查询重写 SELECT mview_name, rewrite_enabled FROM user_mviews WHERE mview_name = '你的物化视图名称';
-
-
物化视图数据过时:
-
如果 MV 状态是
UNUSABLE或数据太旧(STALE),且参数QUERY_REWRITE_INTEGRITY设置为STALE_TOLERATED以外的值,优化器可能不敢用。修改QUERY_REWRITE_INTEGRITY参数:sql--会话级 ALTER SESSION SET QUERY_REWRITE_INTEGRITY = STALE_TOLERATED; --系统级(针对动态参数,立即生效,不需重启数据库) ALTER SYSTEM SET QUERY_REWRITE_INTEGRITY = STALE_TOLERATED SCOPE=BOTH; -
检查状态 :
sqlELECT MVIEW_NAME, STALENESS, COMPILE_STATE FROM USER_MVIEWS WHERE MVIEW_NAME = '你的物化视图名';STALENESS为FRESH是最佳状态。COMPILE_STATE必须是VALID。 -
权限不足:
- 当前用户必须有访问该物化视图的权限。
-
不满足重写条件:
- 你的 SQL 查询的列或过滤条件超出了物化视图定义的范围(例如 MV 只聚合了
dept_id,但你查询了job_id,除非 MV 包含job_id或使用了更高级的重写技术,否则无法匹配)。
- 你的 SQL 查询的列或过滤条件超出了物化视图定义的范围(例如 MV 只聚合了
-