Oracle物化视图概述

物化视图是一种特殊的视图,普通视图不会对数据进行单独存储,而物化视图会定期(取决于物化视图定义的刷新策略)对相应的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;

避坑指南

  1. FAST 刷新失败 :如果你指定了 REFRESH FAST 但没有创建 MATERIALIZED VIEW LOG,或者查询语句太复杂(如使用了 ROWNUM、非主键连接的复杂 Join),刷新会报错或自动降级为 COMPLETE。
  2. 主键要求 :建议基表必须有主键。如果基表没有主键,创建日志时必须用 WITH ROWID,但这会限制 MV 的功能(例如不支持某些聚合函数的快速刷新)。
  3. 列名明确 :在 AS SELECT 语句中,尽量不要用 SELECT *,明确写出列名是更安全的做法。
  4. 物化视图与基表归属用户不是同一个用户时,需显示声明指定基表,物化视图用户需显示授权(GRANT SELECT ON ... TO ...),直接授权角色权限方式,可能会报错"表或视图不存在";使用快速增量刷新时,物化视图用户也需要具备物化视图日志表的查询权限,物化视图日志表需跟基表同属一个用户。

判断SQL 查询是否利用了物化视图

要判断一段 SQL 查询是否真正利用了物化视图(即发生了查询重写 Query Rewrite ),最准确的方法是查看执行计划

如果物化视图生效,执行计划中会出现物化视图的名称,或者出现 MAT_VIEW REWRITE 相关的操作。

以下是三种最常用的验证方法,按推荐程度排序:

方法一:使用 EXPLAIN PLAN(最常用)

这是最直接的方法,用于查看优化器"计划"如何执行这条语句。

操作步骤:

  1. 执行解释计划命令

    将你的 SQL 语句放入 EXPLAIN PLAN FOR 后面执行。

    sql 复制代码
    EXPLAIN PLAN FOR
    SELECT department_id, SUM(salary) 
    FROM employees 
    GROUP BY department_id;
  2. 查看执行计划

    使用 DBMS_XPLAN 包显示结果。

    sql 复制代码
    SELECT * 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 内部大概发生了以下步骤:

  1. 解析器 :"用户查 SELECT dept, SUM(sal) FROM emp。"
  2. 重写器 :"我手里有个物化视图 MV_EMP_DEPT 好像存了这个数据。让我看看......嗯,表对得上,列也有,聚合函数也对(语义匹配成功)。"
  3. 优化器 :"好,核算一下。查基表 EMP 需要 1000 个 IO,查物化视图 MV_EMP_DEPT 只要 10 个 IO。成本更低!(COST成本计算成功)"
  4. 检查员 :"视图状态是 FRESH,数据可信。(完整性检查通过)"
  5. 执行器 :"把 SQL 偷偷换成 SELECT * FROM MV_EMP_DEPT,执行!"

一句话总结 :监测靠执行计划 ,原理靠语义匹配成本比较

如果你发现执行计划里只有原表,没有物化视图,通常是因为以下原因(按可能性排序):

  1. 未开启查询重写

    • 会话级开启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 = '你的物化视图名称';
  2. 物化视图数据过时

    • 如果 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;
    • 检查状态

      sql 复制代码
      ELECT MVIEW_NAME, STALENESS, COMPILE_STATE 
      FROM USER_MVIEWS 
      WHERE MVIEW_NAME = '你的物化视图名';

      STALENESSFRESH 是最佳状态。COMPILE_STATE 必须是 VALID

    • 权限不足

      • 当前用户必须有访问该物化视图的权限。
    • 不满足重写条件

      • 你的 SQL 查询的列或过滤条件超出了物化视图定义的范围(例如 MV 只聚合了 dept_id,但你查询了 job_id,除非 MV 包含 job_id 或使用了更高级的重写技术,否则无法匹配)。
相关推荐
fundoit2 小时前
MySQL Workbench中的权限设置不生效
数据库·mysql
ZzzZZzzzZZZzzzz…2 小时前
MySQL备份还原方法2----LVM
linux·运维·数据库·mysql·备份还原
i220818 Faiz Ul2 小时前
教育资源共享平台|基于springboot + vue教育资源共享平台系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·教育资源共享平台
M--Y2 小时前
Redis常用数据类型-2
数据库·redis
不会写DN2 小时前
SQL 单表操作全解
java·服务器·开发语言·数据库·sql
tang777892 小时前
小红书平台用什么代理IP?数据采集IP封禁解决方法
数据库·爬虫·python·网络协议·ip
XDHCOM2 小时前
ORA-23336: priority group不存在故障修复远程处理
数据库·oracle
Leon-Ning Liu2 小时前
Oracle 26ai新特性:SQL Firewall(SQL 防火墙)的使用方法
数据库·sql·oracle
XDHCOM2 小时前
MySQL ER_IB_MSG_919报错解析,故障修复与远程处理指南
数据库·mysql·adb