ClickHouseNotes

第 03 章:表引擎全景

zjc 于 2026-01-03 发布

这是《ClickHouse 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 表引擎是 ClickHouse 的核心抽象。它决定数据如何写入、如何合并、是否支持副本、能否对接外部系统,以及查询时的行为边界。

3.1 表引擎分类

分类 代表引擎 典型用途
MergeTree 家族 MergeTree、ReplacingMergeTree 事实表、日志表
副本引擎 ReplicatedMergeTree 高可用
分布式引擎 Distributed 分片集群入口
消息引擎 Kafka 消费消息流
外部引擎 MySQL、URL、S3 外部查询和导入
内存引擎 Memory、Buffer 临时数据
简单引擎 TinyLog、Log 小数据测试
字典 Dictionary 维表映射

MergeTree 家族是生产主力。Distributed 不是数据最终存储,Kafka 也不是长期存储,它们常与 MergeTree 组合使用。

3.2 MergeTree

CREATE TABLE events
(
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type LowCardinality(String),
    amount Decimal64(2)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_type, user_id)
TTL event_date + INTERVAL 13 MONTH;

特点:

  1. 数据以 part 为单位写入;
  2. 后台按规则合并 part;
  3. 排序键决定数据物理顺序;
  4. 支持分区、TTL、采样、投影和副本;
  5. 适合批量追加分析。

3.3 ReplacingMergeTree

用于在合并阶段按排序键去重,并通过版本列决定保留哪一行。

CREATE TABLE orders_local
(
    order_id String,
    updated_at DateTime,
    order_status LowCardinality(String),
    amount Decimal64(2),
    version UInt64
)
ENGINE = ReplacingMergeTree(version)
ORDER BY (order_id);

写入两条相同 order_id 的数据:

INSERT INTO orders_local VALUES
('O001', now() - INTERVAL 10 MINUTE, 'CREATED', 100.00, 1),
('O001', now(), 'PAID', 100.00, 2);

强制合并:

OPTIMIZE TABLE orders_local FINAL;

注意:ReplacingMergeTree 的去重发生在后台合并之后,不保证查询瞬间唯一。稳妥做法是在查询时保留最新版本:

SELECT
    order_id,
    argMax(order_status, version) AS order_status,
    argMax(amount, version) AS amount,
    max(updated_at) AS updated_at
FROM orders_local
GROUP BY order_id;

3.4 SummingMergeTree

用于合并时对数值列求和,排序键相同的非数值列取任意一行。

CREATE TABLE metrics_local
(
    metric_date Date,
    metric_name LowCardinality(String),
    city_id UInt32,
    value Float64
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(metric_date)
ORDER BY (metric_date, metric_name, city_id);

查询时仍要聚合:

SELECT metric_name, sum(value) AS total
FROM metrics_local
WHERE metric_date = today()
GROUP BY metric_name;

适合写入端已经能给出可累加指标的预聚合场景,不适合直接存需要精确还原的明细。

3.5 CollapsingMergeTree

通过 sign = 1sign = -1 抵消状态变化,适合把变更事件转为可折叠记录。

CREATE TABLE user_state_local
(
    user_id UInt64,
    level UInt8,
    sign Int8
)
ENGINE = CollapsingMergeTree
ORDER BY user_id;

插入和抵消:

INSERT INTO user_state_local VALUES (1001, 1, 1);
INSERT INTO user_state_local VALUES (1001, 1, -1);
INSERT INTO user_state_local VALUES (1001, 2, 1);

查询:

SELECT user_id, sum(sign) AS cnt, max(level) AS level
FROM user_state_local
GROUP BY user_id
HAVING cnt > 0;

限制:写入端必须按正确顺序发送正负记录。乱序场景优先考虑 VersionedCollapsingMergeTree

3.6 VersionedCollapsingMergeTree

CREATE TABLE user_state_v2
(
    user_id UInt64,
    level UInt8,
    sign Int8,
    version UInt64
)
ENGINE = VersionedCollapsingMergeTree(sign, version)
ORDER BY user_id;

引擎按版本整理顺序,再按 sign 折叠,适合消息可能重放或乱序的流式链路。设计时仍要保证同一业务状态的正负记录版本关系清晰。

3.7 ReplicatedMergeTree

副本引擎依赖 Keeper 或 ZooKeeper 记录副本日志、块 ID 和Leader 选举信息。

CREATE TABLE events_replicated
(
    event_date Date,
    user_id UInt64,
    event_type LowCardinality(String)
)
ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/events',
    '{replica}'
)
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_type, user_id);

生产事实表通常使用具体的 MergeTree 语义副本版本,例如:

ReplicatedReplacingMergeTree
ReplicatedSummingMergeTree
ReplicatedCollapsingMergeTree

3.8 Distributed

Distributed 表是查询入口,不保存业务数据,而是把查询路由到各分片的本地表。

CREATE TABLE events_all
AS events_replicated
ENGINE = Distributed(
    analytics_cluster,
    currentDatabase(),
    events_replicated,
    rand()
);

常见写入模式:

应用 -> Distributed 表 -> 各分片本地表

或更可控的模式:

Kafka 分区 -> 写入路由层 -> 指定分片本地表

3.9 Kafka 引擎

Kafka 引擎只负责消费消息,通常配合物化视图写入 MergeTree:

Kafka topic -> Kafka 表 -> Materialized View -> MergeTree 表

第 22 章会详细展开。这里要记住:Kafka 表不是长期存储表,断点、消费组和数据落表逻辑必须被监控。

3.10 Memory 与 Buffer

CREATE TABLE temp_result
(
    key String,
    value UInt64
)
ENGINE = Memory;

Memory 表重启即丢失,适合临时结果。Buffer 可以缓冲写入,但故障时存在丢失风险:

CREATE TABLE buffered_events
AS events
ENGINE = Buffer(
    currentDatabase(),
    events,
    16,
    100,
    1000000,
    10000000,
    100000000,
    1000000000
);

如果能在写入端聚合,优先改写入端,而不是依赖 Buffer 规避小写入问题。

3.11 引擎选择决策

是否长期存储?
  否 -> Memory / 外部表 / 临时视图
  是 -> MergeTree 家族

是否需要去重?
  是 -> ReplacingMergeTree + 查询侧 argMax/FINAL

指标是否可累加?
  是 -> SummingMergeTree 或普通 MergeTree + sum

变更是否可用正负记录表达?
  是 -> Collapsing / VersionedCollapsing

是否多副本?
  是 -> Replicated 变体

是否分布式?
  是 -> 本地表 Replicated 变体 + Distributed 入口表

本章小结

表引擎的选择先看数据语义,再看可用性架构。普通 MergeTree 适合明细;ReplacingMergeTree 处理版本化幂等;SummingMergeTree 处理可累加指标;Collapsing 系列处理状态抵消;Replicated 提供高可用;Distributed 提供分片入口。不要把“最终一致”的合并语义误当成实时唯一性保证。

思考题

  1. ReplacingMergeTree 为什么查询时仍可能读到重复数据?
  2. SummingMergeTree 中排序键为什么必须包含聚合维度?
  3. Distributed 表的数据存储在哪里?
  4. Buffer 引擎可能带来什么风险?
  5. 如果订单状态会多次变更且消息乱序,应选择哪个引擎?