
物化视图(MV)的快速刷新(Fast Refresh)机制是Oracle数据仓库和报表系统的核心性能保障,其内部设计精巧而复杂。我们将深入解析其全过程。
第一部分:官方严谨的详细阐述
一、 核心概念:完全刷新 vs. 快速刷新
- 完全刷新(Complete Refresh): 简单粗暴。执行物化视图定义查询的完整语句,重新计算所有数据并覆盖整个物化视图段。对于大表,这非常昂贵且耗时。
- 快速刷新(Fast Refresh): 增量刷新。只应用自上次刷新后,基表上发生的增量更改。这是高性能的关键,但其实现依赖一个核心组件——物化视图日志(Materialized View Log, MV Log)。
二、 物化视图日志(MV Log)的深度解析
MV Log是建立在基表上的一个特殊的表,用于跟踪对基表的所有DML更改。
-
创建与结构:
-- 在基表上创建物化视图日志 CREATE MATERIALIZED VIEW LOG ON orders WITH ROWID, SEQUENCE (order_id, customer_id, order_amount) INCLUDING NEW VALUES;WITH ROWID: 指定日志中记录被修改行的ROWID。这是为基于ROWID的物化视图刷新服务的。WITH PRIMARY KEY: 指定日志中记录被修改行的主键。这是为基于主键的物化视图服务的。两者必选其一,主键更常用。(column_list): 指定要记录的列。如果物化视图的查询涉及这些列的聚合或连接,则必须在此列出。INCLUDING NEW VALUES: 允许记录更新操作的新值,这是快速刷新所必需的。SEQUENCE: 提供一个绝对的事务顺序,确保刷新时更改按正确顺序应用。
-
MV Log的内部结构:
MV Log表(命名为MLOG$_<基表名>)包含以下关键内部列:SNAPTIME$$: 日期类型。标识该日志记录何时被“消费”掉。初始值为01-JAN-4000,表示尚未被任何刷新处理。DMLTYPE$$: 字符串类型。记录操作类型:'I'(Insert),'D'(Delete),'U'(Update)。OLD_NEW$$: 字符串类型。标识该记录包含的是旧值('O')还是新值('N')。对于更新操作,会生成两条记录:一条旧值('O'),一条新值('N')。ROWID或PRIMARY KEY列: 用于定位被修改的行。- 指定的列: 例如
ORDER_AMOUNT。对于更新,如果INCLUDING NEW VALUES被指定,则'N'记录会包含新的ORDER_AMOUNT值。 CHANGE_VECTOR$$: 用于更复杂的物化视图类型(如包含聚合的)。SEQUENCE$$: 数值类型。保证所有DML操作的全局顺序,至关重要。
三、 快速刷新的内部工作流程
当执行 DBMS_MVIEW.REFRESH('sales_mv', 'F') 时,发生以下精确步骤:
阶段一:确定刷新窗口和增量
- 获取刷新时间点: 刷新进程首先确定一个SCN或时间点(
refresh_scn),作为本次刷新的一致性终点。 - 锁定MV Log(短暂): 防止新的更改进入当前刷新窗口。
- 标识增量更改: 刷新进程查询MV Log(
MLOG$_ORDERS),寻找所有SNAPTIME$$ > 上次刷新时间的记录。这些记录就是自上次刷新以来的增量更改集(Delta)。
阶段二:计算增量并应用(最复杂的部分)
此阶段的核心是:将基表上的DML操作,转换为对物化视图的DML操作。这个过程对于不同类型的物化视图(是否聚合,是否连接)极其复杂。我们以一个简单的聚合物化视图为例。
-
物化视图定义:
CREATE MATERIALIZED VIEW sales_summary_mv BUILD IMMEDIATE REFRESH FAST ON COMMIT AS SELECT customer_id, SUM(order_amount) total_sales, COUNT(*) order_count FROM orders GROUP BY customer_id; -
基表
ORDERS上的操作:INSERT INTO orders VALUES (100, 1, 200);– 为客户1插入一笔200元的订单UPDATE orders SET order_amount = 300 WHERE order_id = 100;– 将该订单更新为300元DELETE FROM orders WHERE order_id = 100;– 删除该订单
-
刷新进程的逻辑:
-
对于INSERT: MV Log中会有一条
DMLTYPE$$='I',OLD_NEW$$='N'的记录。刷新进程会计算这个INSERT对物化视图的影响:customer_id=1的组,total_sales需要+200,order_count需要+1。它会对物化视图执行一个UPDATE:UPDATE sales_summary_mv SET total_sales = total_sales + 200, order_count = order_count + 1 WHERE customer_id = 1; -
对于UPDATE: MV Log中会有两条记录:
- 一条
DMLTYPE$$='U',OLD_NEW$$='O',包含旧的order_amount=200。 - 一条
DMLTYPE$$='U',OLD_NEW$$='N',包含新的order_amount=300。
刷新进程会计算这个UPDATE的净效应:customer_id=1的组,total_sales需要+(300 - 200) = +100。它会产生一个UPDATE:UPDATE sales_summary_mv SET total_sales = total_sales + 100 WHERE customer_id = 1;注意:order_count不变。
- 一条
-
对于DELETE: MV Log中会有一条
DMLTYPE$$='D',OLD_NEW$$='O'的记录,包含被删除行的值order_amount=300。刷新进程计算:customer_id=1的组,total_sales需要-300,order_count需要-1。它会产生一个UPDATE:UPDATE sales_summary_mv SET total_sales = total_sales - 300, order_count = order_count - 1 WHERE customer_id = 1;
-
阶段三:清理与提交
- 标记已消费记录: 刷新进程将本次处理的所有MV Log记录的
SNAPTIME$$字段更新为当前刷新时间(如SYSDATE)。这表明这些记录已被消费,后续的快速刷新将不再处理它们。 - 提交事务: 提交对物化视图的所有更改和对MV Log的更新。
- Purge操作: (可选)可以设置定期任务来清理
SNAPTIME$$不是4000的旧记录,以控制MV Log的大小。
四、 ON COMMIT vs ON DEMAND
ON COMMIT: 上述的刷新过程会在基表事务提交后立即自动触发。这保证了物化视图与基表的实时强一致性,但会给提交操作带来额外开销,因为提交需要等待刷新完成。ON DEMAND: 刷新过程由用户或作业手动调用。提交基表事务很快,但物化视图数据是 stale 的,直到下次刷新。
五、 争用、等待事件、排查与解决
场景: 高并发的OLTP系统对基表进行大量DML操作,同时物化视图设置了 ON COMMIT 快速刷新。
1. MV Log的争用
- 争用原因: 每个对基表的DML操作都需要在MV Log中插入记录。这会产生对MV Log表的激烈竞争,特别是针对其唯一索引的争用。
- 等待事件:
enq: TX - index contention或buffer busy waits发生在MV Log的表或索引块上。 - 排查:
-- 查看MV Log的大小和增长情况 SELECT segment_name, bytes/1024/1024 size_mb FROM dba_segments WHERE segment_name LIKE 'MLOG$_%'; -- 查看等待事件 SELECT event, count(*) FROM v$session_wait WHERE event LIKE '%index%' OR event LIKE '%buffer busy%' GROUP BY event; - 解决:
- 定期清理MV Log: 对于
ON DEMAND刷新的MV,在刷新后执行DBMS_MVIEW.PURGE_MVIEW_FROM_LOG或手动删除已消费的记录。 - 使用主键而非ROWID: 主键通常是更稳定的标识符。
- 评估刷新频率: 如果可能,将
ON COMMIT改为ON DEMAND并在业务低峰期刷新。
- 定期清理MV Log: 对于
2. 刷新性能问题
- 问题原因: 增量更改集非常大,计算增量并应用的过程本身很慢。对于
ON COMMIT,这直接导致提交变慢。 - 等待事件: 刷新过程本身可能体现为CPU消耗和大量的DML操作 on the materialized view itself.
- 排查:
-- 查看物化视图的刷新状态和最后一次刷新时间 SELECT mview_name, staleness, last_refresh_type, last_refresh_date FROM user_mviews; -- 查看刷新时是否发生错误 SELECT * FROM user_mview_logs; -- 或检查跟踪文件 - 解决:
- 优化物化视图查询: 确保基表上有合适的索引。
- 调整刷新窗口: 对于
ON DEMAND,更频繁地刷新可以避免单次刷新量过大。 - 考虑使用分区: 结合分区交换操作,可以实现近乎瞬时的刷新。
六、 常用管理SQL语句
-- 1. 创建物化视图日志(基于主键)
CREATE MATERIALIZED VIEW LOG ON orders WITH PRIMARY KEY, ROWID, SEQUENCE (order_amount) INCLUDING NEW VALUES;
-- 2. 创建快速刷新物化视图
CREATE MATERIALIZED VIEW sales_summary_mv
REFRESH FAST ON DEMAND -- 或 ON COMMIT
AS
SELECT customer_id, SUM(order_amount) total_sales, COUNT(*) order_count
FROM orders
GROUP BY customer_id;
-- 3. 手动刷新物化视图
BEGIN
DBMS_MVIEW.REFRESH('sales_summary_mv', 'F'); -- 'F' for Fast
-- DBMS_MVIEW.REFRESH('sales_summary_mv', 'C'); -- 'C' for Complete
END;
-- 4. 检查物化视图是否支持快速刷新
SELECT mview_name, rewrite_enabled, refresh_mode, refresh_method
FROM user_mviews;
-- 5. 检查物化视图日志中的记录数(判断增量大小)
SELECT COUNT(*) FROM MLOG$_ORDERS WHERE snaptime$$ = TO_DATE('4000-01-01', 'YYYY-MM-DD'); -- 未刷新的记录
-- 6. 清理物化视图日志
-- 方法1: 手动删除
-- DELETE FROM MLOG$_ORDERS WHERE snaptime$$ < SYSDATE - 7;
-- 方法2: 使用DBMS_MVIEW包
EXEC DBMS_MVIEW.PURGE_MVIEW_FROM_LOG('SALES_SUMMARY_MV');
第二部分:通俗易懂的解释
让我们用一个比喻来理解这个复杂的过程。
把物化视图想象成一个数据仓库的“摘要报告”。
- 基表(Orders): 是前线零售店的每一笔详细销售流水。
- 物化视图(sales_summary_mv): 是总部的每日销售摘要,按客户统计总销售额和订单数。
- 物化视图日志(MV Log): 是零售店经理准备的每日变动清单。每次有销售、退货(Insert/Delete)或者修改订单金额(Update),他就在这个清单上记一笔:“新增:客户A,销售额200元”;“修改:订单X,原金额200,新金额300”。
完全刷新: 就是总部每天下班后,让零售店把整个销售数据库传过来,总部自己重新计算一遍摘要。这非常慢,而且网络压力大。
快速刷新: 是高效的做法。
- 每天下班,零售店经理只需要把那份变动清单(MV Log) 传真给总部。
- 总部的分析师(刷新进程)拿到清单。
- 他拿出昨天的摘要报告(物化视图),根据清单上的记录直接修改今天的报告:
- 看到“新增:客户A,200元”,他就在客户A的总销售额上 +200,订单数 +1。
- 看到“修改:订单X,200→300”,他就算出差额是 +100,然后在客户A的总销售额上 +100。(订单数不变)
- 看到“删除:…”,他就做减法。
- 分析师处理完所有变动后,在清单上盖个“已处理”的章(更新
SNAPTIME$$),然后把新的摘要报告归档。 ON COMMIT意味着零售店每完成一笔交易,不仅要记流水,还要立刻更新变动清单,并且马上传真给总部,总部必须立即更新摘要。这保证了总部的报告绝对是实时的,但零售店结账(提交)的顾客就得等传真完成,体验稍差。ON DEMAND则是零售店经理把变动清单先收着,等总部打电话来要的时候(或者定时,比如每小时一次)再传真过去。这样结账快,但总部的报告不是实时的。
争用是什么?
- 如果零售店生意太好,顾客挤在一起结账,经理桌上一时间堆满了太多人要他记变动清单(DML争用),他就忙不过来了,顾客就要等待。
- 如果变动清单长得离谱,总部的分析师要算很久(刷新性能),那么要么传真机一直占线(
ON COMMIT提交慢),要么总部拿到清单后要算半天才能出报告(ON DEMAND刷新慢)。
快速刷新的精妙之处在于它只传输和处理“增量变化”,避免了大规模的数据移动,这正是其性能优势的根本来源。理解其内部机制,对于设计和维护高效的数据仓库和报表系统至关重要。
欢迎关注我的公众号《IT小Chen》
6911

被折叠的 条评论
为什么被折叠?



