这是《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;
特点:
- 数据以 part 为单位写入;
- 后台按规则合并 part;
- 排序键决定数据物理顺序;
- 支持分区、TTL、采样、投影和副本;
- 适合批量追加分析。
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 = 1 和 sign = -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 提供分片入口。不要把“最终一致”的合并语义误当成实时唯一性保证。
思考题
- ReplacingMergeTree 为什么查询时仍可能读到重复数据?
- SummingMergeTree 中排序键为什么必须包含聚合维度?
- Distributed 表的数据存储在哪里?
- Buffer 引擎可能带来什么风险?
- 如果订单状态会多次变更且消息乱序,应选择哪个引擎?