物化视图与投影
用户经常会问:在什么情况下应该使用物化视图,而什么时候应该使用投影?在本文中,我们将探讨二者之间的关键差异,以及在某些场景下为何可能更适合选择其中之一。
关键差异概览
下表总结了在不同考量维度下,物化视图与投影之间的关键差异。
| Aspect | Materialized views | Projections |
|---|---|---|
| Data storage and location | 将结果存储在一个单独、显式的目标表中,在向源表插入数据时充当 INSERT 触发器。 | 投影会创建经过优化的数据布局,这些数据在物理上与主表数据一起存储,并且对用户不可见。 |
| Update mechanism | 在对源表执行 INSERT 时(对于增量物化视图)同步运行。注意:也可以通过可刷新的物化视图按计划进行调度刷新。 | 在对主表执行 INSERT 时,在后台进行异步更新。 |
| Query interaction | 使用物化视图时需要直接查询目标表,这意味着在编写查询时,用户必须意识到物化视图的存在。 | 投影由 ClickHouse 查询优化器自动选择,对用户是透明的,用户无需为利用投影而修改针对该表的查询。从 25.6 版本开始,还可以基于多个投影进行过滤。 |
Handling UPDATE / DELETE | 对源表上的 UPDATE 或 DELETE 操作不会自动做出反应,因为物化视图并不了解源表,仅作为对源表的 INSERT 触发器。这可能导致源表和目标表之间的数据陈旧,需要通过变通方案或定期全量刷新(通过可刷新的物化视图)来解决。 | 默认情况下,与 DELETED 行不兼容(尤其是轻量级删除)。可以通过 lightweight_mutation_projection_mode(v24.7+)启用兼容性。 |
JOIN support | 支持。可刷新的物化视图可用于复杂的反规范化。增量物化视图仅在最左侧表插入时触发。 | 不支持。在投影定义中不支持使用 JOIN 操作来过滤物化后的数据。不过,查询在与包含投影的表进行 JOIN 时可以正常工作——投影会优化单个表的访问。 |
WHERE clause in definition | 支持。可以包含 WHERE 子句以在物化之前过滤数据。 | 不支持。在投影定义中不支持使用 WHERE 子句来过滤物化后的数据。 |
| Chaining capabilities | 支持,一个物化视图的目标表可以作为另一个物化视图的源表,从而实现多阶段流水线。 | 不支持。投影不能进行链式组合。 |
| Applicable table engines | 可用于多种源表引擎,但目标表通常属于 MergeTree 家族。 | 仅可用于 MergeTree 家族表引擎。 |
| Failure handling | 在插入数据期间发生故障意味着目标表中的数据会丢失,从而导致潜在的不一致性。 | 故障在后台被静默处理。查询可以无缝混合已物化和未物化的数据分片。 |
| Operational overhead | 需要显式创建目标表,并且通常需要手动回填历史数据。使用 UPDATE/DELETE 保持一致性会增加复杂度。 | 投影自动维护并保持同步,通常具有更低的运维负担。 |
FINAL query compatibility | 通常兼容,但往往需要在目标表上使用 GROUP BY。 | 无法与 FINAL 查询配合使用。 |
| Lazy materialization | 支持。 | 使用延迟物化特性时需要监控投影兼容性问题。可能需要设置 query_plan_optimize_lazy_materialization = false。 |
| Parallel replicas | 支持。 | 不支持。 |
optimize_read_in_order | 支持。 | 支持。 |
| Lightweight updates and deletes | 支持。 | 不支持。 |
对比物化视图和投影
何时选择物化视图
在以下情况下,应考虑使用物化视图:
- 处理 实时 ETL 和多阶段数据管道:需要在数据到达时执行复杂转换、聚合或路由分发,并且可能通过串联视图跨多个阶段处理。
- 需要 复杂的反规范化:需要将来自多个源(表、子查询或字典)的数据预先关联(join)到单个、针对查询优化的表中,尤其是在可以接受使用可刷新的物化视图进行周期性全量刷新时。
- 希望获得 显式的 schema 控制:需要一个具有自己 schema 和引擎的独立目标表来存放预计算结果,从而在数据建模上获得更大的灵活性。
- 希望在 摄取时进行过滤:需要在数据被物化之前对其进行过滤,从而减少写入目标表的数据量。
何时应避免使用物化视图
在以下情况下,应考虑避免使用物化视图:
- 源数据经常被更新或删除:如果没有额外的策略来处理源表和目标表之间的一致性,增量物化视图可能会变得过时且不一致。
- 更倾向于保持简单并依赖自动优化:例如你希望避免管理单独的目标表时。
何时选择使用投影(projections)
在以下情况下,应当考虑使用 projections:
- 为单个表优化查询:你的主要目标是通过提供备用排序顺序、优化不属于主键的列上的过滤条件,或为单个表预先计算聚合,从而加速对单个基础表的查询。
- 你希望实现查询透明性(query transparency):你希望在不修改查询的情况下,始终针对原始表执行查询,并依赖 ClickHouse 为给定查询自动选择最优的数据布局。
何时应避免使用 Projection
在以下情况下,应考虑避免使用 Projection:
- 需要复杂数据转换或多阶段 ETL 时:Projection 定义不支持
JOIN操作,不能通过链式组合构建多步管道,也无法处理某些 SQL 特性,如窗口函数或复杂的CASE表达式。虽然针对带有 Projection 的表执行查询时可以自由使用JOIN,但 Projection 本身并不适合承担复杂的数据转换逻辑。 - 需要对物化数据进行显式过滤时:Projection 在定义中不支持使用
WHERE子句来过滤将被物化到该 Projection 中的数据。 - 使用非 MergeTree 表引擎时:Projection 仅适用于使用
MergeTree系列表引擎的表。 - 需要使用
FINAL查询时:Projection 无法与FINAL查询配合使用,而FINAL查询有时会用于去重。 - 需要使用 parallel replicas 时:Projection 不支持该特性。
总结
物化视图和投影(projection)都是用于优化查询和转换数据的强大工具,我们并不建议把它们看作非此即彼的二选一方案。相反,你可以将二者组合、互补使用,以最大化查询性能。因此,在 ClickHouse 中选择物化视图还是投影,很大程度上取决于你的具体用例和访问模式。
作为一个经验法则,当你需要将一个或多个源表中的数据聚合到目标表,或者需要在大规模数据上执行复杂转换时,应优先考虑使用物化视图。物化视图非常适合将代价高昂的聚合工作从查询时刻前移到写入时刻。它们非常适用于按日或按月的汇总(rollup)、实时看板以及数据概览或汇总等场景。
另一方面,当你需要优化的查询在过滤条件中使用的列,与表主键(决定数据在磁盘上物理排序的列)不同的时候,应考虑使用投影。尤其是在无法再更改表主键,或者你的访问模式比主键能够支持的情况更加多样化时,投影会格外有用。