五个交互演示:① INSERT 触发器动画 · ② AggregatingMergeTree State/Merge 流水线 · ③ 链式 MV 拓扑 · ④ Projection 优化器决策 · ⑤ MV vs Projection 对照卡
每点一次「+ INSERT 一批」,源表会写入 5~12 行。MV 立刻被触发,把这一批数据按 toStartOfHour + url 聚合一遍写到目标表。
FROM events_raw 不是"查源表全表",而是把本次 INSERT 的 Block 当作 FROM 的输入。所以同一组 (hour, url) 在不同 INSERT 里会写入多条到目标表,靠 AggregatingMergeTree 后台合并。
UV 这种「全局唯一计数」不能直接 uniq() 存数字 —— 因为 MV 每次只看一批数据。正确做法是把每一批装成 HLL 草图(uniqState),由引擎合并多个草图,查询时再 uniqMerge 还原。
本次 INSERT 的 uid:
每批生成一个 HLL 草图:
引擎把多个草图合并:
查询时还原最终 UV:
xxxState = 装罐头,xxxMerge = 开罐头吃。两者必须成对,中间靠 AggregatingMergeTree 自动堆叠合并。
xxxMergeState(合并后还存为状态,给下一层用);最终查询用 xxxMerge(合并后输出最终值)。
点击下方任意节点,查看每层的表结构、SQL 和数据示例。链式 MV 的精髓:每层数据量缩 60 倍,但靠 State 列保留原始 HLL/TDigest,所以 UV / 分位数依旧准确。
点击上方节点查看详情。
AggregateFunction(uniq, UInt64) 这种 State 类型,下游 MV 用 uniqMergeState() "把状态再聚合成新状态",最终查询才用 uniqMerge()。
主表 events_raw 上有两个 Projection:
p_by_url:按 (url, ts) 重排序的副本p_hour_agg:按 (hour, url) 预聚合 count() / uniq(uid)下面输入一条查询,看优化器选了哪个投影:
| 候选 Projection | ORDER BY / GROUP BY | 能否覆盖 | 预计扫描行 |
|---|
FINAL / SAMPLE;新增 Projection 没 MATERIALIZE,旧 Part 上没有它。
👉 选它当:实时大屏 / 跨表汇聚 / 链式分层
👉 选它当:同表多查询模式 / 二级排序加速
| 维度 | Materialized View | Projection |
|---|---|---|
| 物理位置 | 独立的目标表 | 主表 Part 子目录 |
| 查询是否要改 SQL | 必须查目标表 + xxxMerge | 业务 SQL 不变 |
| 支持跨表聚合 | 多 MV → 同一目标表 | 不支持 |
| 历史数据回填 | POPULATE / 手动补 | MATERIALIZE PROJECTION |
| DROP / 修改成本 | DROP MV 要重灌目标表 | ALTER DROP PROJECTION |
| 存储放大 | 一个 MV = 一张表 | 主 Part 的额外子目录 |
| FINAL 兼容 | 需要业务侧主动查目标表 | 使用 FINAL 时被绕过 |
| 典型场景 | 实时大屏 / 跨表 ETL / 链式 | 同表多模式查询 |