用户常问的一个问题是:什么时候该使用 materialized views,什么时候该使用 投影。本文将探讨两者之间的关键区别,以及在某些场景下 为什么你可能会选择其中一种而不是另一种。
关键差异总结
下表总结了 materialized view 与投影在各个考量方面的关键差异。
| 方面 | Materialized views | 投影 |
|---|---|---|
| 数据存储与位置 | 将结果存储在单独、显式指定的目标表中,并在向源表执行 INSERT 时充当插入触发器。 |
投影会创建优化的数据布局,并在物理上与主表数据一同存储,对用户不可见。 |
| 更新机制 | 对源表的 INSERT 同步执行 (对于增量materialized view) 。注意:也可以通过可刷新materialized view 按计划定期刷新。 |
在向主表 INSERT 后于后台进行异步更新。 |
| 查询交互 | 使用 Materialized Views 时,需要直接查询目标表,这意味着你在编写查询时必须知道 materialized view 的存在。 | 投影由 ClickHouse 的查询优化器自动选择,也就是说对用户是透明的;用户无需修改针对带有投影的表的查询即可利用它。从 25.6 版本开始,还可以按多个投影进行过滤。 |
处理 UPDATE / DELETE |
对源表上的 UPDATE 或 DELETE 操作不会自动响应,因为 materialized view 并不了解源表,只会在向源表插入时充当插入触发器。这可能导致源表与目标表之间的数据变陈旧,并需要变通方案或定期全量刷新 (通过可刷新materialized view) 。 |
默认情况下,与已 DELETED 的行不兼容 (尤其是轻量级删除) 。lightweight_mutation_projection_mode (v24.7+) 可启用兼容性。 |
JOIN 支持 |
是。可刷新materialized view 可用于复杂的反规范化。增量materialized view 仅会在最左侧表插入时触发。 | 否。投影定义中不支持用 JOIN 操作过滤已 materialized 的数据。不过,连接带有投影的表的查询仍可正常工作——投影优化的是各个表的访问。 |
定义中的 WHERE 子句 |
是。可以包含 WHERE 子句,以便在 materialization 之前过滤数据。 |
否。投影定义中不支持用 WHERE 子句过滤已 materialized 的数据。 |
| 链式能力 | 是。一个 materialized view 的目标表可以作为另一个 materialized view 的源,从而支持多阶段管道。 | 否。投影不能链式使用。 |
| 适用的表引擎 | 可用于多种源表引擎,但目标表通常属于 MergeTree 家族。 |
仅适用于 MergeTree 家族表引擎。 |
| 故障处理 | 数据插入期间发生故障意味着目标表中的数据会丢失,从而可能导致不一致。 | 故障会在后台被静默处理。查询可以无缝混合已 materialized 和未 materialized 的 parts。 |
| 运维开销 | 需要显式创建目标表,而且通常需要手动回填。维护与 UPDATE/DELETE 相关的一致性会增加复杂性。 |
投影会自动维护并保持同步,通常运维负担更低。 |
FINAL 查询兼容性 |
通常兼容,但往往需要在目标表上使用 GROUP BY。 |
不适用于 FINAL 查询。 |
| 延迟 materialization | 是。 | 使用 materialization 功能时,请注意投影兼容性问题。你可能需要设置 query_plan_optimize_lazy_materialization = false |
| 并行副本 | 是。 | 否。 |
optimize_read_in_order |
是。 | 是。 |
| 轻量级更新和删除 | 是。 | 否。 |
比较 materialized views 与 投影
何时选择 materialized views
在以下情况下,你应考虑使用 materialized views:
- 处理 实时 ETL 和多阶段数据管道 时:你需要在数据到达时执行复杂的转换、聚合,或对数据进行路由,并且可能需要通过串联视图跨多个阶段处理。
- 你需要 复杂的反规范化:你需要将来自多个来源 (表、子查询或字典) 的数据预先 JOIN 到一张针对查询优化的单一表中,尤其是在可以接受使用可刷新materialized view定期执行全量刷新的情况下。
- 你希望 显式控制 schema:你需要为预计算结果使用一张独立且明确的目标表,并拥有其自身的 schema 和 engine,从而为数据建模提供更大的灵活性。
- 你希望在 摄取时进行过滤:你需要在数据被 materialized 之前 进行过滤,从而减少写入目标表的数据量。
何时应避免使用 materialized views
在以下情况下,应考虑避免使用 materialized views:
- 源数据经常更新或删除:如果没有额外策略来处理源表与目标表之间的一致性,增量materialized view 可能会变得陈旧,从而导致数据不一致。
- 更看重简洁性和自动优化:如果你希望避免管理单独的目标表。
何时选择 投影
在以下情况下,你应考虑使用 投影:
- 优化单个表的查询:你的主要目标是通过提供替代排序顺序、优化主键之外列上的过滤条件,或为单个表预先计算聚合,来加速单个基表上的查询。
- 你希望具备查询透明性:也就是说,你希望查询无需修改即可直接针对原始表执行,并依赖 ClickHouse 为给定查询选择最佳的数据布局。
何时应避免使用投影
在以下情况下,应考虑避免使用投影:
- 需要复杂的数据转换或多阶段 ETL:投影定义不支持
JOIN操作,无法串联形成多步骤管道,也无法处理某些 SQL 功能,例如窗口函数或复杂的CASE语句。虽然对包含投影的表进行查询时可以自由使用 join,但投影本身并不适合复杂的数据转换。 - 需要对 materialized 数据进行显式过滤:投影定义中不支持
WHERE子句,因此无法过滤将被 materialized 到投影中的数据。 - 使用非 MergeTree 表引擎:投影仅适用于使用
MergeTree引擎家族的表。 FINAL查询必不可少:投影无法与FINAL查询配合使用,而后者有时会用于去重。- 如果需要并行副本,也应避免使用投影,因为投影不支持该功能。
总结
materialized views 和 投影 都是优化查询和转换数据的强大工具。通常,我们不建议把它们视为非此即彼的选择。相反,两者可以互为补充,结合使用,以充分发挥查询性能。因此,在 ClickHouse 中选择 materialized views 还是 投影,实际上取决于你的具体使用场景和访问模式。
一般来说,如果你需要将一个或多个源表中的数据聚合到目标表,或者在大规模场景下执行复杂转换,就应考虑使用 materialized views。materialized views 非常适合将高成本聚合的工作从查询时前移到写入时。对于按日或按月的 rollup、实时仪表盘或数据汇总,它们都是很好的选择。
另一方面,如果你需要优化那些按与表主键不同的列进行过滤的查询,则应使用 投影。主键决定了数据在磁盘上的物理排序。尤其是在无法再更改表主键,或者你的访问模式比主键所能覆盖的场景更多样时,投影 会特别有用。