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

前一天我们系统学习了 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 会增加周期性刷新任务、额外存储和新鲜度治理。今天的目标,是把功能理解转化为可执行的设计方法。
本日学习目标
- 理解物化视图在 Doris 查询加速体系中的位置;
- 区分逻辑 View、Query Cache、同步 MV 和异步 MV;
- 掌握同步 MV 的强一致维护机制、适用场景与语法限制;
- 掌握异步 MV 的 MTMV 结果表、Refresh Job 和 Task;
- 掌握
BUILD、REFRESH、ON MANUAL / SCHEDULE / COMMIT; - 理解分区增量刷新、Partition Roll-up 和版本映射;
- 理解透明改写、谓词补偿、聚合 Roll-up、Join Rewrite 和 Cost 选择;
- 掌握
grace_period、分区可用性和 Union Rewrite; - 建立物化视图的创建、验证、监控、替换、下线与回滚流程;
- 完成一份可进入生产评审的物化视图设计与验收报告。
版本口径
截至 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 继续过滤状态、日期或其他字段。剩余数据仍需执行以下动作:
- 从事实表读取订单列;
- 从客户维表读取城市和等级;
- 对
user_id构建或探测 Hash Join; - 对 Join 结果执行分组;
- 在多个 BE 之间交换聚合键;
- 合并局部聚合结果;
- 返回最终报表。
当查询模式长期稳定,物化视图可以提前完成第 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 的当前定位

早期 Doris 用户习惯使用 Rollup 这个名称。当前官方文档统一使用 Sync Materialized View。理解历史名称有助于阅读旧课件、旧 DDL 和线上系统。
同步 MV 的核心特征有四个:
- 基于单张内部表;
- 创建历史数据的构建任务是异步的;
- 构建完成后,新写入在同一事务中同步维护;
- 用户查询基表,优化器自动选择同步 MV。
1. 强一致从哪里来

一次 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. 四个稳定场景

场景 A:单表预聚合
基表保存订单明细,同步 MV 按日期和渠道保存 COUNT、SUM:
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,可以包含 WHERE、GROUP BY、ORDER BY。当前官方 SQL 手册明确列出以下限制:
- 基表必须是一张表,不能使用子查询;
- 不支持
JOIN、HAVING、LIMIT、LATERAL 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;
创建任务处于 RUNNING、WAITING_TXN 或其他未完成状态时,查询改写不会稳定命中。空基表也可能让命中验证缺少足够的成本差异。
四、异步物化视图:一张结果表和一套刷新任务

异步 MV 的底层是一张 MTMV 内部表。它可以引用 Duplicate、Unique、Aggregate 等模型的基表,也可以引用受支持 Catalog 的外部表。物化视图自身按 Duplicate Key 结果表实现,分区、分桶和索引可以继续参与查询加速。
创建异步 MV 时,Doris 会完成两件事:
- 创建物化结果表;
- 注册对应的 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. 一种稳妥的演进路线
- POC:
BUILD DEFERRED + REFRESH AUTO ON MANUAL; - 首次上线:调度平台在上游完成后手动触发;
- 稳定期:固定频率任务转为
ON SCHEDULE; - 低频批量基表:评估
ON COMMIT; - 任一阶段保留
COMPLETE补刷和回滚方案。
六、分区增量刷新:物化视图可扩展性的关键

一张非分区异步 MV 只有一个分区。任意基表数据变化,都可能要求整张结果重算。数据规模从数亿增长到数百亿后,全量刷新会占用大量 CPU、内存、磁盘和网络。
分区 MV 通过版本映射缩小刷新范围。Doris 会记录上一次成功刷新时使用的基表分区版本。下一次刷新开始时,系统比较当前版本,找出受影响的 MV 分区。
1. 日分区映射到日分区
基表 fact_orders 按 order_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 JOIN 或 UNION,各表的分区粒度需要对齐。该能力更复杂,建议从单一事实表分区跟踪开始,完成目标版本回归后逐步扩展。
5. 只保留热点窗口
partition_sync_limit 和 partition_sync_time_unit 可以让 MV 只同步最近 N 天、月或年。它适合“明细保留三年,报表只加速最近三个月”的场景。随着时间推进,刷新任务自动增加新分区并移除超出窗口的旧分区。
