Databend 增量物化视图:基于 Change Tracking 的增量刷新与一致性读取

导读:物化视图并不只是缓存查询结果。本文将深入解析 Databend 如何通过 Change Tracking、Checkpoint 和 Read Fix,在降低刷新成本的同时保证查询正确性,并帮助数据工程师判断增量物化视图适合哪些应用场景。


刷新可以延迟,结果不能过期

物化视图(Materialized View,MV)通过预先计算并持久化高频查询结果,以额外的存储空间换取更低的查询延迟和扫描成本。然而,在生产环境中,真正困难的并不是"保存一份查询结果",而是在源表持续变化时,同时控制刷新成本、保证结果正确、避免阻塞写入,并让聚合状态、物理布局和数据生命周期能够独立维护。

Databend 新物化视图以独立的只读 FUSE 表保存预计算结果,利用 Change Tracking 识别自上次刷新以来的变化,并通过 Checkpoint 记录已经消费的源表位置。它将"异步维护"和"一致性读取"分开处理:刷新任务可以暂时落后,但读取路径会根据变化类型选择直接读取、合并未物化增量或安全回源。因此,刷新延迟首先影响查询性能,而不会让查询静默返回错误的旧结果。

本文将依次介绍主流物化视图的维护模型、Databend 的核心设计、刷新与一致性读取机制、聚合状态和存储维护方式,以及当前推荐的使用场景。


物化视图的实现模型与设计权衡

物化视图通常需要在两个维度上做出选择:一是何时维护 ,即随写入同步更新,还是在后台异步刷新;二是如何维护,即只处理发生变化的数据,还是重新执行完整查询。不同产品会组合这些策略。下表比较的是几种常见实现模型,而不是限定某个产品只能采用其中一种方式。

实现模型 典型方式 查询新鲜度 对写入的影响 维护成本与适用性
同步增量维护 在写入事务内同步更新派生结果,常见于 insert-triggered 或写入驱动的 MV 提交后立即最新 写放大、锁竞争和失败面进入写入路径;复杂聚合可能显著拉长写入延迟 适合写入较轻、低延迟读取优先、派生逻辑简单的场景
异步全量刷新 定时或显式重新执行定义查询,并原子替换结果 刷新间隔内可能陈旧,除非回源计算 通常不阻塞普通写入;刷新时需要扫描和重写全量数据 实现直观、语义稳定;适合中小表、低频刷新,以及更新或删除较多的聚合场景
异步增量刷新 基于日志、Stream、Change Data Capture(CDC)或 Snapshot 差异,只处理变化区间 取决于调度;需要额外机制避免读到旧结果 写入只需保留可追踪的变化,通常不等待 MV 计算完成 适合 append-only 事实表、事件表和高频刷新;要求可靠的变更语义与 Checkpoint
异步增量刷新 + 查询补偿 异步物化存量;查询时合并尚未消费的增量,无法补偿时回源 结果可以保持最新 不把刷新工作放入写入事务,但读路径在刷新落后时需要额外计算 适合既要写入吞吐又要求查询正确性的分析系统;需要明确的补偿条件

Databend 如何回答四个关键问题

Databend 选择异步维护物化结果,并根据源表的真实变化决定采用增量刷新还是全量重建。为了不把刷新延迟转化为错误结果,查询端再根据物化进度选择 Fresh、Hybrid 或 Live Fallback 路径。

关键问题 同步更新 异步更新 Databend 的选择
是否影响写入? 会,派生计算属于写入提交路径 通常不会等待 MV 刷新完成 刷新固定一个源 Snapshot;后续写入可以继续提交到更新的 Snapshot
刷新时是否总是扫描全表? 不一定,但复杂变更通常需要昂贵的维护 可以选择全量或增量 初次刷新,以及聚合视图遇到 UPDATE/DELETE 时执行全量刷新;纯追加时执行增量刷新
刷新落后时能否读取? 一般不存在刷新落后 取决于产品:可能读取旧结果或回源 Fresh 直接读取,Hybrid 执行 read fix,其他情况回源;不会返回错误的旧结果
如何处理复杂聚合? 在写入时维护状态或限制可用算子 可以重算,但成本会随数据规模增长 存储 Aggregate State;遇到无法安全撤销的变更时回退为全量重建

Databend 新物化视图的核心模型

Databend 新物化视图不是附着在源表 Block 上的索引,而是一张特殊的只读 FUSE 表,其引擎为 engine = MATERIALIZED_VIEW。它拥有独立的 Table ID、TableMeta、Snapshot、Segment、Block、Cluster Key 和存储参数,因此可以独立表达刷新进度,并按照自身的查询模式维护物理布局。

从 Aggregating Index 到独立存储

Aggregating Index 属于 Databend 早期的物化视图模型,它类似一个 block 级别的 Projection 索引伴随原表流动,设计意图上与新物化视图都能够减少重复计算,但两者的存储归属、刷新进度和维护方式并不相同。

维度 Aggregating Index 新物化视图
存储归属 依附于源表 Block 使用独立 FUSE 存储
物理布局 受源 Block 和源表维护影响 可以独立设置 Cluster Key、Block 与维护策略
查询方式 主要由优化器透明使用 可以直接查询,也可以由优化器改写使用
刷新进度 难以表达独立消费端点 持有源表 Checkpoint 与独立 Snapshot
源表 compact/recluster 旧 Block 变化后通常需要重建相应索引 MV 存储可以独立 compact/recluster

元数据与事务边界

为了同时描述物化结果、定义查询和源表依赖关系,物化视图在 Meta 中保存三类信息。

元数据 关键内容 目的
TableMeta(mv_id) Physical Schema、MV Snapshot、源表 ID/Sequence、源 Snapshot Location 描述可读写的独立表与刷新端点
MVDefinition(mv_id) 原始查询、重写后的 Physical Query、Logical Schema 保存体积较大的定义内容,避免频繁更新 TableMeta 带来写放大
SourceTableMV(source_id, mv_id) 源表绑定与 Generation 维护源表到 MV 的依赖关系

物化视图的创建、替换和删除,会将表元数据、MV 定义以及源表依赖关系放入同一个 Meta 事务。创建物化视图时,系统也会原子启用源表的 Change Tracking,从而避免出现"MV 已经发布,但源表变化尚不可追踪"的中间状态。


从刷新到读取:如何保证结果正确

Databend 将刷新进度、物化数据和读取路径连接成一套完整机制。刷新端通过 Change Tracking 和 Checkpoint 消费源表变化;读取端则比较 MV Checkpoint Snapshot 与源表当前 Snapshot,判断已有结果是否足以直接使用。

Change Tracking 与 Checkpoint:识别并记录变化

Databend 复用 Stream 的 change-table 语义,读取两次消费端点之间发生的变化,而不是为物化视图另建一套 CDC。变化记录包含以下内部列:

内部列 含义
change$action 变化动作,例如 INSERTDELETE
change$is_update 是否属于一次更新
change$row_id 源表行的稳定身份

刷新成功时,物化视图数据和以下 Checkpoint 会在同一个事务中提交:

Plaintext 复制代码
materialized_view_source_table_seq
materialized_view_source_snapshot_location

原子提交保证系统不会出现"数据已经写入但 Checkpoint 没有推进",或者"Checkpoint 已经推进但数据尚未写入"的状态。即使某批增量完全被物化视图的 WHERE 条件过滤,Checkpoint 也会继续推进,避免后续刷新重复消费同一批变化。

刷新策略:根据真实变化选择增量或全量

刷新开始后,系统会锁定物化视图本身,重新加载定义,并固定本轮使用的源 Snapshot。刷新期间产生的后续写入仍然可以提交到更新的 Snapshot,不会被纳入当前批次。最终采用哪种刷新方式,由两个消费端点之间的真实变化决定。

源表端点状态 刷新动作 原因
第一次刷新 使用 INSERT OVERWRITE 执行全量计算 尚无起始 Checkpoint,需要建立初始结果
仅有 INSERT 对增量执行 Physical MV Query,再执行 INSERT INTO Append-only 变化可以安全追加 Aggregate State 或结果行
非聚合 MV 出现 UPDATE/DELETE 根据 _mv_source_row_id 在内部执行 MERGE 通过源行稳定身份,将删除和更新准确投射到 MV
聚合 MV 出现 UPDATE/DELETE 使用 INSERT OVERWRITE 全量重建 MINMAX 和近似去重等聚合状态无法通用、正确地撤销
Snapshot 未变化 仅处理 Checkpoint 语义 没有新的数据需要物化
源表为空 使用空端点 避免不必要的扫描

对于聚合物化视图,纯追加刷新写入的是聚合状态 ,而不是最终结果。同一个 Group Key 即使暂时分布在多个 Block 中,后续读取仍可通过 sum_mergecount_mergemin_merge 等函数合并状态,保证结果正确。

三种读路径:Fresh、Hybrid 与 Live Fallback

读取物化视图时,Binder 会比较 MV Checkpoint Snapshot 与源表当前 Snapshot,并选择以下三种路径之一。

读路径 触发条件 读取计划 正确性与成本
Fresh 两个 Snapshot 相同 直接扫描 MV Physical Storage;聚合 MV 再执行 State Merge 成本最低,结果最新
Hybrid(read fix) MV 落后,但 Checkpoint 之后只有 Append-only 变化 Persisted MV Storage UNION ALL Physical MV Query(Source Delta),再合并状态 查询时临时补齐增量;不修改 MV,也不等待后台刷新
Live Fallback 尚未首次刷新、增量包含更新或删除、历史 Snapshot 已清理,或者源表绑定失效 忽略 MV 存储,基于源表执行原始逻辑查询 成本较高,但不会返回陈旧或错误的结果

Read fix 是一种查询计划补偿,并不是后台自动刷新。由此可以得到一个重要保证:刷新延迟首先表现为性能差异,而不是结果正确性差异。


聚合状态与独立存储如何持续维护

增量物化不仅要解决"如何写入新增结果",还要保证不同批次的聚合状态能够正确合并,并控制长期刷新产生的状态行和文件碎片。

Logical Schema 与 Physical Schema

用户查询时看到的逻辑列,不一定等同于物理写入的列。例如,avg(amount) 不能只存储一个平均值,再对不同增量批次的平均值继续求平均;为了得到正确结果,系统必须保存可以合并的中间状态。

层次 avg(amount) 示例 作用
Logical Schema average_amount 用户查询时看到的结果列
Physical Schema sum_state(amount)count_state(amount) 保存增量写入和跨 Block 合并所需的状态
读取投影 sum_merge(sum_state) / count_merge(count_state) 将物理状态还原为逻辑结果

当前初步支持的主流聚合包括 summinmaxavgcountapprox_count_distinct。其中,avg 会被重写为 sum_state + count_state。非聚合物化视图的 Physical Schema 则会额外保存 _mv_source_row_id,用于 Standard 刷新过程中的行定位。

Reaggregate、Compact 与 Recluster

经过多次 append-only 刷新后,同一个 Group Key 可能产生多份状态行。此时结果仍然正确,但 State Merge 的输入数量和文件碎片会持续增加。Databend 会在 compact 或 recluster 写出新 Block 之前,对该 Block 内相同 Group Key 的 Aggregate State 执行 reaggregate。

操作 执行内容 不包含的行为
Reaggregate 在本次新 Block 内按照业务列分组,合并重复的 Aggregate State 不保证跨全表或跨全部 Block 一次性收敛
OPTIMIZE TABLE mv COMPACT 整理 MV Block,并在输出 Block 上触发 reaggregate 不允许用户改变 MV 的逻辑数据
ALTER MATERIALIZED VIEW mv RECLUSTER [FINAL] 按照 MV 自身的 Cluster Key 重整物理布局 不继承源表的聚簇布局

对于包含 GROUP BY、但不包含聚合函数的物化视图,维护过程也会执行去重。非聚合物化视图会保存 _mv_source_row_id,因此两条业务列相同、但对应不同源记录的数据,不会在 compact 过程中被错误合并。


查询改写与完整使用流程

用户既可以直接查询物化视图,也可以继续查询源表。优化器会匹配输出表达式、过滤条件、Group Key、聚合函数、聚合粒度和查询所需列;匹配成功后,源表查询会被改写为物化视图的读取计划。改写后的计划仍会根据数据状态选择 Fresh、Hybrid 或 Live Fallback 路径。

以下示例创建一个按客户汇总已支付订单的物化视图:

SQL 复制代码
CREATE MATERIALIZED VIEW paid_orders_by_customer
    (customer_id, total_amount, order_count, average_amount)
    CLUSTER BY (customer_id)
AS
SELECT
    customer_id,
    sum(amount),
    count(*),
    avg(amount)
FROM orders
WHERE paid
GROUP BY customer_id;

物化视图创建时只发布定义,并不会立即填充数据。第一次刷新之前,直接查询物化视图会走 Live Fallback,因此结果仍然正确,但暂时无法获得物化存储带来的加速。完成首次刷新后,后续纯追加变化可以增量消费;随着增量批次增加,还可以按需整理物理存储。

SQL 复制代码
-- 第一次刷新,建立初始结果
REFRESH MATERIALIZED VIEW paid_orders_by_customer;

-- 后续只有新增订单时,刷新仅消费增量
REFRESH MATERIALIZED VIEW paid_orders_by_customer;

-- 随增量批次增加,按需整理存储
OPTIMIZE TABLE paid_orders_by_customer COMPACT;
ALTER MATERIALIZED VIEW paid_orders_by_customer RECLUSTER FINAL;

使用场景与选型建议

Databend 新物化视图的收益取决于源表的变化模式、定义查询的复杂度,以及刷新和查询对性能与正确性的要求。它更适合变化可以安全增量消费的单表分析场景,而不是现阶段所有持续计算任务的通用替代方案。

推荐使用的场景

  • 日志、埋点、事件与事实表。 这类数据通常以 INSERT 为主,历史记录较少更新或删除,因此可以持续使用低成本的 append-only 增量刷新。随着新数据进入,物化视图只需要处理尚未消费的变化,而不必重复计算整张源表。

  • 高频 Group By、指标看板和客户或区域汇总。 对固定维度反复执行聚合的查询,可以利用 Aggregate State 增量合并结果;当查询形态匹配时,优化器还可以透明地将源表查询改写为物化视图读取,从而减少明细数据扫描和重复计算。

  • 写入吞吐优先,但不能接受旧结果。 刷新任务异步执行,不需要把物化计算放入源表写入事务;当刷新暂时落后且新增变化可以安全合并时,read fix 会在查询阶段补齐增量。这样既能避免刷新阻塞写入,也不会为了低延迟而返回已过期的结果。

  • 源表与查询需要不同物理布局。 当源表的写入方式和下游查询模式不同,物化视图可以使用独立的 Cluster Key、compact 和 recluster 策略。例如,源表可以围绕写入时间组织,而物化结果可以按照客户或业务维度重新布局。

使用前需关注事项

  • 定义查询目前聚焦单表 FUSE 场景。 物化视图当前只能基于 default Catalog 中的一张持久化 FUSE 表创建,支持简单的 SELECT ... FROM ... [WHERE ...] [GROUP BY ...]。多表查询、JOIN、子查询、集合运算、窗口函数和非确定性函数暂不在支持范围内。因此,如果工作负载依赖复杂宽表建模或多表持续聚合,仍需要使用其他转换与编排方式。

  • 聚合物化视图对更新和删除存在重建成本。 非聚合物化视图可以借助 _mv_source_row_idUPDATEDELETE 准确投射到物化结果;但对于聚合物化视图,为了优先保证正确性,一旦源表出现 UPDATEDELETE,当前实现会执行全量重建,尚未支持分区级增量重算。频繁更新或删除的大型聚合表需要特别评估刷新成本。

  • 刷新调度仍需显式配置。 当前可以通过 Task 定时执行 REFRESH MATERIALIZED VIEW,自动生成刷新任务属于后续 Databend Cloud 的演进方向。read fix 也不是刷新机制本身:只有 append-only 增量能够走 Hybrid 路径,无法安全补偿时,查询会回到源表执行原始逻辑。

  • 增量能力依赖 Change Tracking 的历史连续性。 如果增量刷新或读取补偿所需的历史 Snapshot 已经被清理,系统将无法基于这些历史变化继续增量处理,此时读路径会选择回源。数据保留策略、刷新频率和 Change Tracking 的可用范围需要结合写入速度与查询要求统一规划。

  • 物化视图是只读对象。 普通的 INSERTUPDATEDELETE 会被拒绝,用户不能直接修改物化结果;物理存储的整理也受到专用维护操作的限制。这一约束可以保护物化结果与 Checkpoint 之间的一致性,但也意味着它不能被当作普通业务表写入。


增量维护的价值最终取决于正确性

Databend 新物化视图的核心并不只是"把查询结果缓存起来",而是建立了一条完整的增量计算链路:Change Tracking 识别变化,Checkpoint 记录消费位置,Physical Schema 保存可合并状态,刷新过程以原子事务推进数据与端点,read fix 则在异步维护期间保证一致性读取。

这套机制尤其适合持续写入、以追加为主,并且需要稳定分析结果的单表场景。面对多表依赖、频繁更新或删除,以及需要自动调度的工作负载,数据工程师应评估全量重建成本和后续 Dynamic Table 能力,而不应将当前物化视图视为通用的持续计算引擎。全面理解这些能力,才能判断物化视图究竟是在减少计算,还是把刷新和维护成本转移到了另一个环节。

增量物化视图特性在 Databend 企业版 v1.2.934 后引入和更新。

相关推荐
Raas10021 分钟前
MAI Gateway(魔芋企业级AI网关)详解:AI网关在架构中的位置,一文读懂企业AI流量治理
大数据·人工智能·架构·gateway·ai网关·mai gateway
腾讯云开发者36 分钟前
与数据库打了几十年交道,WorkBuddy让我有了「第二知识库」
数据库
星云API技术支持1 小时前
企业微信二次开发机器人:实时消息回调与历史消息同步如何配合
数据库·机器人·企业微信
2601_962074581 小时前
大数据-260 实时数仓 - 项目背景与需求 实时数仓架构 需求分析 技术选型 逻辑架构
大数据·架构
LPCK_202606221 小时前
从自动化到自主化:生物制药智能体的人机边界怎么设计
大数据·人工智能·自动化
Aolith1 小时前
我在React全栈项目中搭建了一个完整题库
数据库·react.js·全栈
衡石科技2 小时前
HENGSHI BOX|全域智控,私域安全的ChatBI一体机
大数据·人工智能·安全
雨辰AI2 小时前
信创数仓分层建模|国产数据库 ODS/DWD/DWS 分层落地规范(金仓 / 达梦 / 高斯适配)
数据库·云原生·政务
张工在路上2 小时前
WPF 布局基础概述
大数据·hadoop·wpf