第 17 关 · ★★★

物化视图与透明改写

同步 Rollup、异步 MV 与透明改写:预计算的正确打开方式。

已点亮 · 最佳 分
物化视图与透明改写 第 1 页

Day 12|Doris 物化视图:同步 Rollup、异步 MV、刷新与透明改写

Day 12 学习总览
Day 12 学习总览

前一天我们系统学习了 Prefix、ZoneMap、BloomFilter、NGram BloomFilter 和 Inverted Index。它们的共同目标很清晰:尽早排除无关分区、Tablet、Segment、Page 和 RowID,让存储层少读数据,让执行层少做过滤、聚合和网络交换。

今天进入另一条查询加速主线:预计算

很多经营报表每天被反复执行。SQL 可能包含数亿行事实表、多个维表 JOIN、复杂过滤、去重、聚合和表达式计算。即使索引已经尽力缩小数据范围,系统仍需在每次查询中重新完成相同的计算。用户越多,重复消耗越大,P95、P99 也越容易受到并发、缓存和资源争抢影响。

物化视图把一部分高频计算提前完成,并把结果持久化保存。查询到来时,优化器可以直接读取物化结果,或在物化结果之上补充少量过滤和聚合。扫描量、Join 开销、聚合行数和 Shuffle 数据量会同时下降。

Doris 4.x 提供两套物化视图机制:

  • 同步物化视图(Sync Materialized View):历史上常称为 Rollup。它依附于一张基表,在数据写入时同步维护,和基表保持强一致。
  • 异步物化视图(Async Materialized View):底层是一张 MTMV 结果表,按照手动、定时或提交触发策略刷新,支持多表 JOIN 和更复杂的预计算。

两者都能参与查询自动改写,生命周期、能力边界和成本结构有明显差异。同步 MV 会增加每次导入的工作量;异步 MV 会增加周期性刷新任务、额外存储和新鲜度治理。今天的目标,是把功能理解转化为可执行的设计方法。

本日学习目标

  1. 理解物化视图在 Doris 查询加速体系中的位置;
  2. 区分逻辑 View、Query Cache、同步 MV 和异步 MV;
  3. 掌握同步 MV 的强一致维护机制、适用场景与语法限制;
  4. 掌握异步 MV 的 MTMV 结果表、Refresh Job 和 Task;
  5. 掌握 BUILDREFRESHON MANUAL / SCHEDULE / COMMIT
  6. 理解分区增量刷新、Partition Roll-up 和版本映射;
  7. 理解透明改写、谓词补偿、聚合 Roll-up、Join Rewrite 和 Cost 选择;
  8. 掌握 grace_period、分区可用性和 Union Rewrite;
  9. 建立物化视图的创建、验证、监控、替换、下线与回滚流程;
  10. 完成一份可进入生产评审的物化视图设计与验收报告。

版本口径

截至 2026-08-16,Apache Doris 官网将 4.1.3 标记为 Latest,将 4.0.8 标记为 Stable。本文以 Doris 4.x 当前文档为基线,实验优先在 4.0.8 和 4.1.3 上分别回归。部分能力会随 Minor 版本继续演进,尤其是 Window Function 透明改写、外部 Catalog 的分区变化感知和多源分区刷新,需要以目标版本实测结果为准。

实验前提

完整包提供 120,000 行固定种子订单数据、5,000 行客户维表数据、同步 MV、异步 MV、刷新、监控、透明改写和正确性对账 SQL。当前内容制作环境没有连接实际 Doris 集群,SQL 已完成静态检查,运行结果需要在 Day 5 搭建的目标环境中确认。


一、从索引到预计算:查询加速的第二条主线

从索引到预计算
从索引到预计算

索引和物化视图解决的问题可以放在同一条查询链路中理解。

假设一张订单明细表有 20 亿行,下面的经营分析每天执行数千次:

SELECT
    o.order_date,
    c.city,
    c.customer_level,
    COUNT(*) AS order_count,
    SUM(o.amount) AS revenue
FROM fact_orders o
JOIN dim_customer c
  ON o.user_id = c.user_id
WHERE o.status = 'PAID'
  AND o.order_date BETWEEN '2026-08-01' AND '2026-08-16'
GROUP BY
    o.order_date,
    c.city,
    c.customer_level;

分区裁剪可以把扫描范围缩到 16 天。Sort Key、ZoneMap 和 Inverted Index 继续过滤状态、日期或其他字段。剩余数据仍需执行以下动作:

  1. 从事实表读取订单列;
  2. 从客户维表读取城市和等级;
  3. user_id 构建或探测 Hash Join;
  4. 对 Join 结果执行分组;
  5. 在多个 BE 之间交换聚合键;
  6. 合并局部聚合结果;
  7. 返回最终报表。

当查询模式长期稳定,物化视图可以提前完成第 3~6 步。查询时扫描的是已经按日期、城市、等级聚合的结果,行数可能从数亿下降到几万。

可以把性能收益粗略写成:

查询收益
≈ 明细扫描减少
 + Join 计算减少
 + 聚合输入减少
 + Shuffle 数据减少
 - MV 读取和补偿计算

对应的维护成本为:

维护成本
≈ 同步写放大或异步刷新资源
 + 额外存储
 + 元数据和任务治理
 + 正确性与新鲜度验证

物化视图适合高频、稳定、计算重的查询族。偶发 SQL、频繁变化的维度组合、小数据表和极端实时场景,通常先依赖表结构、索引、执行计划或业务层缓存。


二、先分清四个概念:逻辑 View、缓存、同步 MV、异步 MV

物化视图的概念边界
物化视图的概念边界

1. 逻辑 View

逻辑 View 保存一段 SQL 定义,不持久化查询结果。用户查询 View 时,优化器会展开定义,再访问底层表。它适合封装复杂 SQL、统一语义、控制访问入口和简化权限。

逻辑 View 自身不会减少底层计算。相同复杂查询被调用一千次,底层 Join 和聚合仍可能执行一千次。

2. Query Cache

Query Cache 面向重复查询结果。SQL 文本、参数、版本和缓存条件满足时,系统直接返回缓存结果。它适合高频重复、数据变化可感知、结果集较小的场景。

缓存通常具有更强的短期性。数据版本变化、非确定性函数、参数变化和内存淘汰都会影响命中。物化视图则拥有明确的表结构、存储布局、分区、分桶和刷新合同。

3. 同步物化视图

同步 MV 依附于一张基表。创建完成后,每次写入都会同时生成基表和同步 MV 的数据版本。用户继续查询基表,优化器根据查询结构和成本选择物化视图。

同步 MV 不能作为独立表直接查询。它更接近基表内部的一组附加物理结构。

4. 异步物化视图

异步 MV 是一张可直接查询的 MTMV 内部结果表。创建时注册 Refresh Job,每次刷新产生一个 Task,并通过预计算 SQL写入结果。用户可以直接查询 MV,也可以保留原 SQL,让 Nereids 完成透明改写。

5. 一张速查表

机制 持久化结果 一致性 维护方式 查询入口 适合场景
逻辑 View 随基表实时 无数据维护 直接查询 View 语义封装、权限、复用 SQL
Query Cache 内存结果 依赖版本与失效规则 自动缓存与淘汰 原 SQL 完全重复的短周期查询
同步 MV 强一致 写入时同步维护 原表 SQL 自动改写 单表聚合、备用排序、预过滤
异步 MV 最终一致 手动、定时或提交触发 直接查询或自动改写 多表 Join、复杂聚合、湖仓加速

三、同步物化视图:Rollup 的当前定位

同步 MV 与异步 MV 对比
同步 MV 与异步 MV 对比

早期 Doris 用户习惯使用 Rollup 这个名称。当前官方文档统一使用 Sync Materialized View。理解历史名称有助于阅读旧课件、旧 DDL 和线上系统。

同步 MV 的核心特征有四个:

  1. 基于单张内部表;
  2. 创建历史数据的构建任务是异步的;
  3. 构建完成后,新写入在同一事务中同步维护;
  4. 用户查询基表,优化器自动选择同步 MV。

1. 强一致从哪里来

同步 MV 同步维护流程
同步 MV 同步维护流程

一次 Stream Load 或 Flink Connector 写入进入 Doris 后,数据会被路由到目标 Tablet。存在同步 MV 时,导入流程还要完成物化视图定义中的过滤、表达式、排序和聚合,并生成对应 Rowset。

事务只有在基表和全部同步 MV 的数据都满足提交条件后,才会发布可见版本。任一关键步骤失败,整个批次按照事务语义处理。查询不会看到基表已经更新、同步 MV仍停留在旧版本的中间状态。

这份一致性带来确定的写入代价。每增加一张同步 MV,同一批数据就多一套计算、编码、索引、Rowset、Replica 和 Compaction 工作。生产评审必须测量:

  • Stream Load / Routine Load / Flink 写入吞吐变化;
  • BE CPU、内存和磁盘写入峰值;
  • Segment 数量和 Compaction Score;
  • 事务耗时和 Publish Version 延迟;
  • 副本磁盘增量。

2. 四个稳定场景

同步 MV 适用场景
同步 MV 适用场景

场景 A:单表预聚合

基表保存订单明细,同步 MV 按日期和渠道保存 COUNTSUM

CREATE MATERIALIZED VIEW sync_mv_paid_day_channel AS
SELECT
    order_date AS mv_order_date,
    channel AS mv_channel,
    COUNT(*) AS mv_order_count,
    SUM(amount) AS mv_revenue
FROM fact_orders
WHERE status = 'PAID'
GROUP BY order_date, channel
ORDER BY order_date, channel;

高频报表继续查询 fact_orders。当查询的过滤、分组和聚合可以由该 MV 表达时,优化器读取更少的数据行。

场景 B:备用 Prefix Index

一张表只有一套主 Sort Key。业务同时存在“按订单号查”和“按地区 + 日期查”两套稳定路径时,可以创建一张改变列序的非聚合同步 MV。

CREATE MATERIALIZED VIEW sync_mv_region_date AS
SELECT
    region AS mv_region,
    order_date AS mv_order_date,
    order_id AS mv_order_id,
    user_id AS mv_user_id,
    amount AS mv_amount,
    status AS mv_status
FROM fact_orders
ORDER BY region, order_date, order_id;

该 MV 形成新的有序物理结构和前缀索引入口。

场景 C:预过滤

固定报表只关心 status='PAID',同步 MV 可以在写入时过滤其他状态。收益取决于过滤比例。付费订单占全部数据的 95% 时,预过滤价值有限;占 10% 时,扫描量下降更明显。

场景 D:表达式预计算

高频查询反复执行日期截断、条件表达式或确定性转换,可以将表达式结果写入同步 MV。查询命中后减少重复函数计算。

3. 当前语法边界

同步 MV 支持单表 SELECT,可以包含 WHEREGROUP BYORDER BY。当前官方 SQL 手册明确列出以下限制:

  • 基表必须是一张表,不能使用子查询;
  • 不支持 JOINHAVINGLIMITLATERAL VIEW
  • SELECT 列不能包含窗口函数、常量、重复表达式和自增列;
  • 聚合函数需要位于根表达式;
  • 同步 MV 的列名不能与基表列名或同表其他同步 MV 列名冲突;
  • Unique Key 表上的同步 MV只能调整列序,不能降低聚合粒度;
  • Unique Key 和 Aggregate Key 表使用 WHERE 时,只能引用 Key 列。

4. 创建与检查

同步 MV 的首次构建是异步任务。提交 DDL 后需要等待 State=FINISHED

SHOW ALTER TABLE MATERIALIZED VIEW FROM doris_day12;

查看所有物理结构:

DESC fact_orders ALL;

查看创建语句:

SHOW CREATE MATERIALIZED VIEW
sync_mv_paid_day_channel ON fact_orders;

删除:

DROP MATERIALIZED VIEW
sync_mv_paid_day_channel ON fact_orders;

创建任务处于 RUNNINGWAITING_TXN 或其他未完成状态时,查询改写不会稳定命中。空基表也可能让命中验证缺少足够的成本差异。


四、异步物化视图:一张结果表和一套刷新任务

异步 MV 架构
异步 MV 架构

异步 MV 的底层是一张 MTMV 内部表。它可以引用 Duplicate、Unique、Aggregate 等模型的基表,也可以引用受支持 Catalog 的外部表。物化视图自身按 Duplicate Key 结果表实现,分区、分桶和索引可以继续参与查询加速。

创建异步 MV 时,Doris 会完成两件事:

  1. 创建物化结果表;
  2. 注册对应的 Refresh Job。

每次刷新产生一个 Task。刷新逻辑计算最新结果,并通过内部写入更新 MV。官方原理说明将其描述为通过 INSERT OVERWRITE 写入最新数据。

1. 创建语法的骨架

CREATE MATERIALIZED VIEW async_mv_city_level_day
BUILD IMMEDIATE
REFRESH AUTO ON MANUAL
PARTITION BY (order_date)
DISTRIBUTED BY HASH(city, customer_level) BUCKETS 8
PROPERTIES (
    "replication_num" = "1",
    "grace_period" = "0",
    "refresh_partition_num" = "4"
)
AS
SELECT
    o.order_date,
    c.city,
    c.customer_level,
    COUNT(*) AS order_count,
    SUM(o.amount) AS revenue,
    SUM(o.quantity) AS item_count
FROM fact_orders o
JOIN dim_customer c
  ON o.user_id = c.user_id
WHERE o.status = 'PAID'
GROUP BY
    o.order_date,
    c.city,
    c.customer_level;

2. 直接查询和透明改写

异步 MV 支持直接查询:

SELECT *
FROM async_mv_city_level_day
WHERE order_date BETWEEN '2026-08-01' AND '2026-08-16';

这种方式适合明确的数据产品接口、固定 ADS 表或调度链路。调用方知道 MV 的名称和新鲜度合同。

透明改写保留原 SQL。分析师、BI 工具和应用继续查询基表,优化器自动寻找候选 MV。这种方式减少上层改造,并允许 CBO 在 MV 和原表之间按成本选择。

3. 异步 MV 的适合场景

  • 多表 JOIN 后的经营报表;
  • 星型或雪花模型的高频汇总;
  • 固定时间点生成的一致性快照;
  • 计算量较大的清洗、转换和指标层;
  • Hive、Iceberg、Paimon 等外部表的热点数据本地化;
  • Join 结果复用,后续查询再做聚合;
  • 数仓分层中的 DWS、ADS 结果。

基表每分钟多次大规模变化、业务要求秒级强一致、数据量很小、SQL 模式持续变化时,需要先评估刷新成本和实际收益。


五、刷新策略:三个维度组合成一份完整合同

刷新策略三维模型
刷新策略三维模型

异步 MV 的刷新配置由三个独立维度组成。

1. Build Mode:创建后何时做第一次刷新

含义 适用方式
BUILD IMMEDIATE 创建后立即发起首次刷新,默认值 小到中等数据量,创建窗口可控
BUILD DEFERRED 先创建定义,稍后触发刷新 大表、低峰构建、外部调度控制

生产大表通常先使用 DEFERRED,确认 Workload Group、分区映射、资源窗口和基表导入完成,再手动启动首刷。

2. Refresh Method:每次刷新重算多少数据

含义
REFRESH AUTO 能识别变更分区时做增量刷新,无法增量时回退全量
REFRESH COMPLETE 跳过变更判断,重算全部数据

AUTO 需要基表或 Catalog 提供可用的版本信息。JDBC 等无法检测变化的数据源,官方文档要求使用 COMPLETE,否则可能出现基表有数据、MV 未刷新出数据的情况。

3. Refresh Trigger:谁来发起刷新

ON MANUAL

REFRESH MATERIALIZED VIEW async_mv_city_level_day AUTO;

适合由 Airflow、DolphinScheduler、DataWorks 或企业调度平台先检查上游依赖,再提交刷新。它也适合补刷、历史修复和 POC。

ON SCHEDULE

REFRESH AUTO
ON SCHEDULE EVERY 1 HOUR
STARTS '2026-08-17 00:30:00'

适合小时报、日报和可预测刷新窗口。全部 MV 集中在整点刷新会形成资源尖峰,需要按业务等级错峰。

ON COMMIT

REFRESH AUTO ON COMMIT

基表提交后触发刷新。当前官方文档说明该能力自 Doris 2.1.4 起支持。基表更新频率高时,刷新任务会频繁创建,容易形成刷新风暴。使用前需要评估批次频率、单分区刷新耗时和任务合并能力。

4. 一种稳妥的演进路线

  1. POC:BUILD DEFERRED + REFRESH AUTO ON MANUAL
  2. 首次上线:调度平台在上游完成后手动触发;
  3. 稳定期:固定频率任务转为 ON SCHEDULE
  4. 低频批量基表:评估 ON COMMIT
  5. 任一阶段保留 COMPLETE 补刷和回滚方案。

六、分区增量刷新:物化视图可扩展性的关键

分区增量刷新
分区增量刷新

一张非分区异步 MV 只有一个分区。任意基表数据变化,都可能要求整张结果重算。数据规模从数亿增长到数百亿后,全量刷新会占用大量 CPU、内存、磁盘和网络。

分区 MV 通过版本映射缩小刷新范围。Doris 会记录上一次成功刷新时使用的基表分区版本。下一次刷新开始时,系统比较当前版本,找出受影响的 MV 分区。

1. 日分区映射到日分区

基表 fact_ordersorder_date 分区,MV 同样按 order_date 分区。8 月 15 日基表分区发生变化时,系统只刷新 MV 的 8 月 15 日分区。

2. 日分区 Roll-up 到月分区

PARTITION BY (DATE_TRUNC(order_date, 'MONTH'))

基表每天一个分区,MV 每月一个分区。8 月任意一天发生变化,都刷新 MV 的 8 月分区。分区数量下降,单分区重算范围上升,需要结合月度数据量决定粒度。

3. 增量刷新成立的主要条件

  • 至少一张基表使用 Range 或 List 分区;
  • MV 的分区列能映射到基表分区列;
  • MV 分区列出现在 SELECT 列表;
  • 存在 GROUP BY 时,分区列需要出现在分组维度;
  • 存在 Window Function 时,分区跟踪列需要满足窗口分区要求;
  • 分区表达式主要使用标识符和 date_trunc
  • 外连接的 Null 生成侧列通常不适合作为分区跟踪列;
  • 非跟踪表的数据变化可能让系统只能执行全量刷新。

4. 多源分区刷新

当前 4.x 文档已经描述多源 Partition Change Tracking。多个分区表可以共同触发分区刷新,主要支持 INNER JOINUNION,各表的分区粒度需要对齐。该能力更复杂,建议从单一事实表分区跟踪开始,完成目标版本回归后逐步扩展。

5. 只保留热点窗口

partition_sync_limitpartition_sync_time_unit 可以让 MV 只同步最近 N 天、月或年。它适合“明细保留三年,报表只加速最近三个月”的场景。随着时间推进,刷新任务自动增加新分区并移除超出窗口的旧分区。


登录后可阅读本文完整内容。