物化视图(Materialized View)通过预计算和存储查询结果,配合自动查询改写(Query Rewrite),物化视图可在不修改原始 SQL 的情况下提升查询性能,尤其适用于需要加速高频报表、仪表盘查询或复杂多表关联分析的场景。
但是物化视图通过预计算来实现性能优化就意味着其具有一定的存储和数据维护性成本,建议先完成数据类型选择、主键设计等基础优化,再针对需要进一步加速的查询模式使用物化视图。
本文将为您介绍物化视图的概念、两种类型(同步与异步)的原理、使用场景及差异对比。
特点:
优势:
缺点:
ByteHouse 的同步物化视图设计继承自 ClickHouse 社区版的增量物化视图机制,针对云原生架构进行了适配和增强。
注意
ClickHouse 社区版中**“增量物化视图”的语义,主要是指每次仅将本次写入基表的增量数据写入物化视图目标表;与 ByteHouse 基于增量计算(Dynamic Table)**技术实现的增量物化视图和增量视图维护(IVM, Incremental View Maintenance)并不是同一概念。
同步物化视图的基本原理,是在数据写入阶段预先完成聚合、过滤或转换,并将计算结果写入目标表。与原始数据相比,物化后的数据通常规模更小。例如在聚合场景中,目标表只需保存部分聚合结果或草图数据。查询时可以直接基于这些预计算结果进行分析,无需重复扫描原始数据并执行复杂计算,因此查询语句更简单,执行效率也通常更高。
与 Postgres 这类事务型数据库不同,ByteHouse 中的同步物化视图本质上类似一个触发器:当数据块被插入到基表时,它会在这些新插入的数据块上执行一条查询,并物化查询结果。即当基表发生数据写入时,系统会自动触发物化视图定义中的查询逻辑,并将查询结果写入对应的目标表。如果后续又有更多数据写入,物化视图会再次针对新插入的数据块执行查询,并把结果继续写入目标表。目标表中的中间结果会不断被更新和合并。
在单表追加写入,并且聚合逻辑与目标表引擎配置正确的情况下,查询时合并目标表中的中间结果,可以得到与在全部原始数据上执行相应聚合一致的结果。多表 JOIN 和唯一键表等场景存在额外限制,具体请参见下文的“使用限制与注意事项”。
如上图所示,关联表后续的新增、更新或删除不会更新物化视图中已物化的历史结果,因此其结果可能与直接查询基表的结果不一致。这类场景要求关联维表保持不变;若维表发生变化,则需手动重算并更新受影响的历史结果,以恢复一致性。
同步物化视图按每次新写入的数据块执行计算,不会自动继承基表的 Unique Key 去重和更新语义。因此当基表因去重或更新淘汰旧记录时,已经写入物化视图目标表的旧记录或聚合贡献不会随之自动撤回,因此可能与去重后的基表查询结果不一致;目标表自身的合并也不保证消除这类差异。
异步物化视图通过定时调度或手动触发全表或者分区级别的预计算并存储查询结果,减少重复扫描、关联和聚合。配合透明查询改写,可在不修改原始 SQL 的情况下加速查询。
使用异步物化视图前,建议先确认以下要点:
常见的使用场景如下:
场景 | 说明 |
|---|---|
重复聚合查询 | 预先汇总销售额、访问量等常用指标,加速报表和看板查询 |
多表关联查询 | 预计算关联与聚合结果,减少查询时执行复杂 JOIN 的开销,适合事实表批量更新、维表相对稳定的场景 |
异步物化视图将计算过程与基表写入解耦,通过定时调度或手动触发更新物化结果。刷新流程主要包括确定刷新范围、调度执行任务、更新版本信息三个步骤。
刷新触发后,系统检测基表变化,并根据基表与物化视图之间的分区映射确定需要重新计算的范围:
需要注意,分区映射缩小的是刷新范围;被选中的分区仍需完整重算,并非只处理新增记录。
确定刷新范围后,系统会组织并下发调度任务:
每个任务执行物化视图定义的查询,读取对应范围内的基表数据,并通过 INSERT OVERWRITE 等机制覆盖原有物化结果。合理设置并发度,有助于兼顾刷新效率与资源占用,减少对同一计算组中其他业务查询的影响。
刷新成功后,系统会记录本次物化结果对应的基表版本,用于后续变化检测和查询改写的一致性检查。在 ByteHouse 云数仓版中,目标数据覆盖与刷新元数据更新在同一事务中提交,保持物化结果与版本记录一致。
若基表随后再次变化,系统会在后续检查中重新识别受影响的目标分区,并安排刷新。
假设使用异步物化视图对销售表按天、按商品预聚合,统计销售额。
基表和目标表均按销售日期分区,建立“基表某一天的分区 → 目标表同一天的分区”的映射,刷新行为如下:
基表变化 | 分区映射与定位 | 刷新行为 |
|---|---|---|
1 月 1 日新增一批订单 | 基表 1 月 1 日分区 → 目标表 1 月 1 日分区 | 重算当天分区内的全部订单数据,覆盖当天的汇总结果 |
1 月 2 日修正部分订单金额 | 基表 1 月 2 日分区 → 目标表 1 月 2 日分区 | 重算当天分区内的全部订单数据,覆盖当天的汇总结果 |
1 月 3 日没有变化 | 对应目标分区不受影响 | 保留已有结果,无需重算 |
当异步物化视图包含 JOIN(多表关联)时,系统需要分别判断各张基表的变化会影响哪些目标分区。
对于按日期分区的订单事实表,订单数据本身包含订单日期。因此,当某一天的订单发生变化时,系统可以通过日期分区映射,直接定位并刷新对应日期的目标分区。
但商品、用户等维表通常按业务实体组织,其中的数据一般不包含订单日期。一条维表记录可能与多个日期分区中的事实数据关联。因此,维表发生变化时,系统可能无法仅根据这条变更记录,直接判断哪些日期分区受到了影响。
例如,订单事实表按订单日期分区,商品维表记录商品 ID 和商品分类。物化视图将两张表关联,并按日期和商品分类汇总销售额:
数据变化 | 系统能否直接定位日期分区 | 刷新范围 |
|---|---|---|
1 月 2 日新增订单 | 可以。订单记录中包含订单日期 | 仅刷新 1 月 2 日对应的目标分区 |
商品 A 的分类由“数码配件”改为“智能设备” | 不可以。商品记录本身不能说明商品 A 曾在哪些日期产生订单 | 需要扩大刷新范围,按照全部目标分区进行刷新 |
假设商品 A 在 1 月 1 日和 1 月 2 日都有订单,从业务结果看,这两个日期的分类汇总都会受到影响。但系统仅根据商品维表的变更记录,无法直接确定商品 A 关联了哪些日期分区。为保证刷新后的结果完整、正确,需要将全部目标分区标记为待刷新。
因此,即使事实表与目标表之间已经建立了日期分区映射,维表变化仍可能触发范围更大的刷新。包含多表 JOIN 的异步物化视图更适合维表相对稳定的场景;如果维表频繁变化,刷新成本可能明显增加。
异步物化视图的刷新成本很大程度上取决于能否建立基表分区到物化视图目标分区的映射关系,这里分为两种情况讨论:
在多表 JOIN 场景中,即使事实表分区已对齐,关联维表的变化仍可能影响全部目标分区。因此,异步物化视图更适合事实表批量更新、维表相对稳定的场景。
合理设计分区映射、设置刷新范围并配置合理的计算资源,有助于控制刷新成本和数据延迟。
对比项 | 同步物化视图 | 异步物化视图 |
|---|---|---|
数据一致性 | 和基表相同事务写入,与基表强一致 | 异步刷新,提供最终一致性,数据存在一定延迟 |
历史数据处理 | 创建视图前的历史数据,需按业务需求手动回填 | 通过刷新机制自动处理刷新范围内的历史数据 |
多表关联 | 受限支持;关联表变化不会自动更新已物化的历史结果 | 支持;维表变化可能触发全部目标分区刷新 |
数据时效 | 基表写入时同步计算,但会增加写入开销 | 以最近一次成功刷新结果为准,存在刷新延迟 |
查询改写 | 支持 | 支持 |
计算开销 | 计算发生在数据写入过程中,可能增加写入延迟、降低导入吞吐量 | 刷新单独消耗计算资源;建议使用独立计算组,也可根据负载情况复用现有计算组 |
总的来说,同步视图和异步视图各有其优势和适用场景。选择哪种类型的物化视图,需要根据具体的业务需求、数据特征和系统资源情况来权衡。在实际应用中,可能会同时使用这两种类型的视图来满足不同的需求。