第 6 章 · MergeTree 家族进阶

5 个交互演示:① Replacing 去重时机 · ② Summing 自动汇总 · ③ Aggregating 状态合并 · ④ Collapsing 行折叠 · ⑤ FINAL vs argMax 对比

同主键多次写入 → 触发 Merge → 只留 ver 最大的一条

点「插入同主键」模拟对同一个 user_id 的多次更新,点「触发 Merge」让 ReplacingMergeTree 按 version 去重。

磁盘 Part 物理行 0

user_idnicknameversion

SELECT FINAL / argMax 视角 0

user_idnicknameversion
观察: Merge 没触发时,左边有重复行,右边(argMax / FINAL)始终只有去重后的最新值; Merge 触发后,左边也会变成去重后的结果(合并完磁盘就干净)。

同主键多次写入 → Merge 时数值列累加

点「插入一行」模拟 (date, event, user) 相同的新事件,数值列 pv / revenue 在 Merge 时自动累加。

磁盘物理行 0

dateeventuserpvrevenue

GROUP BY 视角(业务查) 0

dateeventusersum(pv)sum(revenue)
观察: Merge 前左右行数不同;Merge 后左边物理行被累加成一条,左右一致。 业务代码 永远要写 GROUP BY + sum(),不要依赖引擎"已经合并"。

AggregatingMergeTree + uniqState:HyperLogLog 状态合并

演示 uniqState(user_id) 写入时生成 HLL 状态(用 32 位"命中位"抽象), Merge 时按位 OR 合并两个状态,最终用 uniqMerge 估算基数。

最终查询:uniqMerge 估算独立用户数

点"生成一行状态"后,再点"Merge 所有状态"查看最终结果
关键:每个 uniqState 是一个**定长状态**(这里简化成 32 位); 合并时按位 OR,不会再占更多空间;uniqMerge 基于命中位数估算基数(真正的 HLL 算法精度更高)。 这就是亿级明细能压成千级状态行的秘密。

CollapsingMergeTree:+1 / -1 成对抵消

演示订单状态翻转的三步:「创建(+1) → 撤销前态(-1) → 写新态(+1)」,Merge 时相邻 +1/-1 被消掉。

磁盘物理行 0

order_idstatusamountSign

业务查询 sum(x*Sign) HAVING sum(Sign)>0

order_idlatest_statuslive_amountlive_rows
注意:CollapsingMergeTree 要求客户端保证「先 -1 再 +1」的顺序,否则折叠失败; 并发写入场景请选择 VersionedCollapsingMergeTree

去重 / 聚合的 3 种查询方式大对比

方式实现正确性性能何时用
SELECT ... FINAL 查询时按主键做一次额外归并,把同主键多行按家族规则收敛 ✅ 精确 ❌ 慢 2-10 倍多一次 sort + merge 扫描 调试 / 低频一次性查询
argMax(col, ver) GROUP BY pk 用聚合函数在查询时自己实现"取最新" ✅ 精确 ✅ 通常比 FINAL 快走正常聚合 pipeline Replacing 高频查询首选
OPTIMIZE TABLE ... FINAL 后台立刻执行一次全表合并,物理把所有 Part 压成一个 ✅ 精确(合并完再查) ❌❌ 巨贵可能跑几小时,阻塞 DDL,不能高频触发 仅离线:压缩历史 / 冷分区归档前
sum(col*Sign) GROUP BY pk HAVING sum(Sign)>0 Collapsing 家族通用查询套路 ✅ 精确 ✅ 正常聚合性能 Collapsing / VersionedCollapsing 查询首选
xxMerge(xxState) GROUP BY pk Aggregating 家族通用查询套路 ✅ 精确(HLL 等估算算法有精度) ✅ 极快只扫聚合状态,毫秒级 Aggregating 查询首选
一句总结: "FINAL 能不用就不用"。所有家族引擎都有配套的聚合函数表达去重/求和/折叠的语义, 这些语义走 CH 正常的**向量化聚合 pipeline**,性能远好于 FINAL 的"二次归并"。

实测样例(千万级明细,单机 M1)

查询read_rowsduration
SELECT count() FROM t10,000,000120 ms基线
argMax(name, ver) GROUP BY id10,000,000650 ms1 次 scan + 聚合
SELECT * FROM t FINAL10,000,0003,200 ms5 倍于 argMax
OPTIMIZE TABLE t FINAL重写整表~60 sI/O 密集
uniqMerge on AggregatingMT5,20035 ms压到状态行