第 10 章 · 物化视图与 Projection

五个交互演示:① INSERT 触发器动画 · ② AggregatingMergeTree State/Merge 流水线 · ③ 链式 MV 拓扑 · ④ Projection 优化器决策 · ⑤ MV vs Projection 对照卡

物化视图 = 焊在源表上的「批级 INSERT 触发器」

每点一次「+ INSERT 一批」,源表会写入 5~12 行。MV 立刻被触发,把这一批数据按 toStartOfHour + url 聚合一遍写到目标表。

📦 源表 events_raw

行数:0 本次 Block:0
触发

⚡ MV (toStartOfHour + url, count, uniq)

⏳ 等待 INSERT...
写入

📊 目标表 events_agg

聚合组:0 累计 PV:0
反直觉点: MV 的 SELECT FROM events_raw 不是"查源表全表",而是把本次 INSERT 的 Block 当作 FROM 的输入。所以同一组 (hour, url) 在不同 INSERT 里会写入多条到目标表,靠 AggregatingMergeTree 后台合并。

实时聚合三件套:AggregatingMergeTree + MV + xxxState/xxxMerge

UV 这种「全局唯一计数」不能直接 uniq() 存数字 —— 因为 MV 每次只看一批数据。正确做法是把每一批装成 HLL 草图(uniqState),由引擎合并多个草图,查询时再 uniqMerge 还原。

1️⃣ 源 Block

本次 INSERT 的 uid:

2️⃣ uniqState() 装罐头

每批生成一个 HLL 草图:

3️⃣ AggMT 后台 Merge

引擎把多个草图合并:

4️⃣ uniqMerge() 出值

查询时还原最终 UV:

口诀: xxxState = 装罐头,xxxMerge = 开罐头吃。两者必须成对,中间靠 AggregatingMergeTree 自动堆叠合并。
什么时候用 Merge vs MergeState? 链式 MV 的中间层用 xxxMergeState(合并后还存为状态,给下一层用);最终查询用 xxxMerge(合并后输出最终值)。

链式物化视图:分钟 → 小时 → 天 三级 Rollup

点击下方任意节点,查看每层的表结构、SQL 和数据示例。链式 MV 的精髓:每层数据量缩 60 倍,但靠 State 列保留原始 HLL/TDigest,所以 UV / 分位数依旧准确。

点击上方节点查看详情。

注意: 中间层的列必须是 AggregateFunction(uniq, UInt64) 这种 State 类型,下游 MV 用 uniqMergeState() "把状态再聚合成新状态",最终查询才用 uniqMerge()

Projection 优化器:业务 SQL 不变,自动选最优投影

主表 events_raw 上有两个 Projection:

  • p_by_url:按 (url, ts) 重排序的副本
  • p_hour_agg:按 (hour, url) 预聚合 count() / uniq(uid)

下面输入一条查询,看优化器选了哪个投影:

EXPLAIN PROJECTIONS = 1 点 "🔍 分析查询" 看 ClickHouse 优化器选谁...
候选 ProjectionORDER BY / GROUP BY能否覆盖预计扫描行
选不中的常见原因: 查询用了 Projection 没存的列;用了 FINAL / SAMPLE;新增 Projection 没 MATERIALIZE,旧 Part 上没有它。

Materialized View vs Projection 选型卡

🟡 Materialized View

  • 本质 焊在源表上的批级 INSERT 触发器
  • 物理 独立的目标表,可任意引擎 (含 Replicated/Distributed)
  • 触发 写入即触发,准实时
  • 业务 SQL ❌ 必须改 — 查目标表 + xxxMerge
  • 跨表 ✅ 多 MV 写一表,链式聚合
  • 历史 需 POPULATE / 手动补 (POPULATE 有数据丢失风险)
  • JOIN 维表 ✅ 推荐配 Dictionary
  • 运维 DROP MV 不影响目标表

👉 选它当:实时大屏 / 跨表汇聚 / 链式分层

🟣 Projection

  • 本质 主表 Part 内嵌的二级目录 / 预聚合
  • 物理 同表 Part 的子目录,引擎自动同步
  • 触发 跟随主表写入,原子
  • 业务 SQL ✅ 零改动 — 优化器自动选
  • 跨表 ❌ 只能投影自己
  • 历史 一行 MATERIALIZE PROJECTION 即可补全
  • JOIN 维表 不参与
  • 运维 DROP / ADD 极轻量

👉 选它当:同表多查询模式 / 二级排序加速

细节对比表

维度Materialized ViewProjection
物理位置独立的目标表主表 Part 子目录
查询是否要改 SQL必须查目标表 + xxxMerge业务 SQL 不变
支持跨表聚合多 MV → 同一目标表不支持
历史数据回填POPULATE / 手动补MATERIALIZE PROJECTION
DROP / 修改成本DROP MV 要重灌目标表ALTER DROP PROJECTION
存储放大一个 MV = 一张表主 Part 的额外子目录
FINAL 兼容需要业务侧主动查目标表使用 FINAL 时被绕过
典型场景实时大屏 / 跨表 ETL / 链式同表多模式查询
不知道选哪个? 默认上 Projection (零改动);不行再用 MV (功能强但要改业务 SQL)。