先说结论

MergeTree 将每批写入保存为不可变数据片段,并在后台持续合并;数据按排序键组织,查询可以通过稀疏主键索引跳过大量无关数据。

写入过程

一次 INSERT 通常形成一个新的 data part。片段内部按 ORDER BY 指定的键排序,并保存列式数据、标记和索引信息。大量极小批次写入会产生过多 parts,增加合并压力。

分区与排序键

PARTITION BY 主要用于数据管理和分区裁剪,不应产生过多细粒度分区;ORDER BY 决定数据的物理排序,是查询性能设计的核心。

CREATE TABLE events (
  event_time DateTime,
  user_id UInt64,
  event_type LowCardinality(String)
) ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, event_time, user_id);

后台合并

后台线程把多个较小片段合并成更大片段。ReplacingMergeTree、SummingMergeTree 等变体会在合并阶段执行去重或聚合,但合并是异步的,查询不能假设数据已经立即完成最终合并。

设计注意点

  • 排序键优先放常用过滤列,但也要考虑基数和查询模式。
  • 避免大量小批量 INSERT。
  • 分区不是越细越好。
  • ClickHouse 更适合追加写和分析查询,不适合高频单行更新事务。

参考资料

Part 和稀疏索引

数据按排序键排序并分列保存,索引按固定粒度记录关键位置。查询先判断哪些 Granule 可能命中,再读取相关列。它牺牲逐行精确定位,换来更小的索引和高吞吐扫描。

常见问题

追问 1:ORDER BY 和 PRIMARY KEY 有何区别?

ORDER BY 决定物理排序;主键表达式可以是其前缀以减小索引,但不提供唯一性约束。

追问 2:为什么会出现 Too many parts?

写入批次过小或分区过多,Part 产生速度超过后台合并速度。应增大批次、优化分区并检查磁盘与合并负载。

追问 3:ReplacingMergeTree 能实时去重吗?

不能。后台合并前多个版本可能同时存在,查询 FINAL 虽可处理但成本较高,应结合版本列和业务查询设计。