目前在 ClickHouse 中,按照特点可以将表引擎大致分成 6 个系列,分别是合并树、外部存储、内存、文件、接口和其他。其中最为核心的当属 MergeTree 系列。
MergeTree 有两层含义:其一,表示合并树表引擎家族;其二,表示家族中最基础的 MergeTree 表引擎。除了基础表引擎外,常用的变种还有 ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree 和 VersionedCollapsingMergeTree。每一种合并树的变种在继承基础 MergeTree 能力之后,又增加了独有的特性。
1.1 MergeTree
MergeTree 作为家族最基础的表引擎,提供了数据分区、一级索引和二级索引等功能。本节进一步介绍 MergeTree 独有的两项能力——数据 TTL 与存储策略。
1.1.1 数据 TTL
TTL 即 Time To Live,表示数据的存活时间。可以为某个列字段或整张表设置 TTL。
TTL time_col + INTERVAL 3 DAY TTL time_col + INTERVAL 1 MONTH
INTERVAL 完整的操作包括:SECOND、MINUTE、HOUR、DAY、WEEK、MONTH、QUARTER 和 YEAR。
1. 列级别 TTL
在定义表字段时声明 TTL 表达式,主键字段不能被声明 TTL:
CREATE TABLE ttl_table_v1 ( id String, create_time DateTime, code String TTL create_time + INTERVAL 10 SECOND, type UInt8 TTL create_time + INTERVAL 10 SECOND ) ENGINE = MergeTree PARTITION BY toYYYYMM(create_time) ORDER BY id;
写入测试数据后,10 秒执行 OPTIMIZE 强制触发 TTL 清理:
OPTIMIZE TABLE ttl_table_v1 FINAL;
过期的列数据会被还原为默认值。修改列字段 TTL:
ALTER TABLE ttl_table_v1 MODIFY COLUMN code String TTL create_time + INTERVAL 1 DAY;
2. 表级别 TTL
CREATE TABLE ttl_table_v2 ( id String, create_time DateTime, code String TTL create_time + INTERVAL 1 MINUTE, type UInt8 ) ENGINE = MergeTree PARTITION BY toYYYYMM(create_time) ORDER BY create_time TTL create_time + INTERVAL 1 DAY;
修改表级 TTL:
ALTER TABLE ttl_table_v2 MODIFY TTL create_time + INTERVAL 3 DAY;
3. TTL 的运行机理
MergeTree 以分区目录为单位,通过 ttl.txt 文件记录过期时间。写入数据时生成 ttl.txt,分区合并时触发 TTL 删除逻辑。
小贴士:
- TTL 默认合并频率由
merge_with_ttl_timeout控制,默认 86400 秒 - 强制触发:
OPTIMIZE TABLE table_name FINAL - 全局启停:
SYSTEM STOP/START TTL MERGES
1.1.2 多路径存储策略
MergeTree 实现了自定义存储策略的功能。
三类存储策略:
| 策略 | 适用场景 | 说明 |
|---|---|---|
| 默认策略 | 单磁盘 | 所有分区写入 path 指定路径 |
| JBOD 策略 | 多磁盘无 RAID | 轮询写入各磁盘 |
| HOT/COLD 策略 | 不同类型磁盘 | 热数据 SSD,冷数据 HDD |
存储配置在 config.xml 中:
<storage_configuration> <disks> <disk_name_a> <path>/chbase/data</path> <keep_free_space_bytes>1073741824</keep_free_space_bytes> </disk_name_a> <disk_name_b> <path>/chbase/hotdata1</path> </disk_name_b> </disks> <policies> <policie_name_a> <volumes> <volume_name_a> <disk>disk_name_a</disk> <disk>disk_name_b</disk> <max_data_part_size_bytes>1073741824</max_data_part_size_bytes> </volume_name_a> </volumes> <move_factor>0.2</move_factor> </policie_name_a> </policies> </storage_configuration>
JBOD 策略示例
<policies> <default_jbod> <volumes> <jbod> <disk>disk_hot1</disk> <disk>disk_hot2</disk> </jbod> </volumes> </default_jbod> </policies>
CREATE TABLE jbod_table (id UInt64) ENGINE = MergeTree() ORDER BY id SETTINGS storage_policy = 'default_jbod';
HOT/COLD 策略示例
<policies> <moving_from_hot_to_cold> <volumes> <hot> <disk>disk_hot1</disk> <max_data_part_size_bytes>1073741824</max_data_part_size_bytes> </hot> <cold> <disk>disk_cold</disk> </cold> </volumes> <move_factor>0.2</move_factor> </moving_from_hot_to_cold> </policies>
移动分区:
ALTER TABLE hot_cold_table MOVE PART 'all_1_2_1' TO DISK 'disk_hot1'; ALTER TABLE hot_cold_table MOVE PART 'all_1_2_1' TO VOLUME 'cold';
1.2 ReplacingMergeTree
支持数据去重。使用 ORDER BY 排序键作为判断重复数据的依据。
CREATE TABLE replace_table ( id String, code String, create_time DateTime ) ENGINE = ReplacingMergeTree() PARTITION BY toYYYYMM(create_time) ORDER BY (id, code) PRIMARY KEY id;
带版本号:
ENGINE = ReplacingMergeTree(create_time) -- 保留同一组中 create_time 最大的那一行
核心逻辑:
- 使用 ORDER BY 排序键作为判断重复数据的唯一键
- 只有在合并分区时才触发删除重复数据
- 以数据分区为单位删除重复数据(跨分区无法去重)
- 无 ver:保留最后一行;有 ver:保留 ver 值最大的行
1.3 SummingMergeTree
按照预先定义的条件在合并时自动聚合汇总数据,将同一分组下的多行合并为一行。
CREATE TABLE summing_table ( id String, city String, v1 UInt32, v2 Float64, create_time DateTime ) ENGINE = SummingMergeTree() PARTITION BY toYYYYMM(create_time) ORDER BY (id, city) PRIMARY KEY id;
可以指定汇总列:SummingMergeTree((v1, v2)),不指定则汇总所有非主键数值类型字段。
核心逻辑:
- ORDER BY 作为聚合 Key
- 合并分区时触发汇总
- 同一分区内相同聚合 Key 的数据合并为一行
- 非汇总字段取第一行数据
- 支持嵌套类型,列名需以 Map 后缀结尾
1.4 AggregatingMergeTree
MergeTree 家族中定义方式最特殊的一个,通常作为物化视图的表引擎。使用 AggregateFunction 数据类型以二进制格式存储中间聚合状态。
CREATE TABLE agg_table ( id String, city String, code AggregateFunction(uniq, String), value AggregateFunction(sum, UInt32), create_time DateTime ) ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(create_time) ORDER BY (id, city) PRIMARY KEY id;
写入数据(使用 *State 函数):
INSERT INTO TABLE agg_table
SELECT 'A000', 'wuhan',
uniqState('code1'),
sumState(toUInt32(100)),
'2019-08-10 17:00:00';
查询数据(使用 *Merge 函数):
SELECT id, city, uniqMerge(code), sumMerge(value) FROM agg_table GROUP BY id, city;
配合物化视图使用(主流用法):
-- 底表(MergeTree) CREATE TABLE agg_table_basic ( id String, city String, code String, value UInt32 ) ENGINE = MergeTree() PARTITION BY city ORDER BY (id, city); -- 物化视图(AggregatingMergeTree) CREATE MATERIALIZED VIEW agg_view ENGINE = AggregatingMergeTree() PARTITION BY city ORDER BY (id, city) AS SELECT id, city, uniqState(code) AS code, sumState(value) AS value FROM agg_table_basic GROUP BY id, city;
1.5 CollapsingMergeTree
通过以增代删的思路支持行级数据修改和删除。使用 sign 标记位:sign=1 表示有效数据,sign=-1 表示需要删除的数据。
CREATE TABLE collapse_table ( id String, code Int32, create_time DateTime, sign Int8 ) ENGINE = CollapsingMergeTree(sign) PARTITION BY toYYYYMM(create_time) ORDER BY id;
修改数据(三笔操作):
-- 原数据
INSERT INTO collapse_table VALUES ('A000', 100, '2019-02-20 00:00:00', 1);
-- 镜像数据(折叠删除)
INSERT INTO collapse_table VALUES ('A000', 100, '2019-02-20 00:00:00', -1);
-- 新数据
INSERT INTO collapse_table VALUES ('A000', 120, '2019-02-20 00:00:00', 1);
折叠规则:
- sign=1 比 sign=-1 多一行 → 保留最后一行 sign=1
- sign=-1 比 sign=1 多一行 → 保留第一行 sign=-1
- 一样多且最后一行是 sign=1 → 保留首尾各一行
- 一样多且最后一行是 sign=-1 → 全部删除
注意事项:
- 折叠不是实时的,只在分区合并时触发
- 查询时需改写:
SELECT id, SUM(code * sign), COUNT(code * sign) FROM collapse_table GROUP BY id HAVING SUM(sign) > 0;
- 对写入顺序有严格要求:必须先写 sign=1 再写 sign=-1
1.6 VersionedCollapsingMergeTree
CollapsingMergeTree 的升级版,对数据写入顺序没有要求。额外指定 ver 版本号字段:
CREATE TABLE ver_collapse_table ( id String, code Int32, create_time DateTime, sign Int8, ver UInt8 ) ENGINE = VersionedCollapsingMergeTree(sign, ver) PARTITION BY toYYYYMM(create_time) ORDER BY id;
版本号字段 ver 自动追加到 ORDER BY 末端(ORDER BY id, ver DESC),保证折叠时回到正确顺序。
1.7 各种 MergeTree 之间的关系总结
1.7.1 继承关系
MergeTree 向下派生出 6 个变种表引擎,共 7 种。它们共用一个主体,在触发 Merge 动作时调用各自独有的合并逻辑。
1.7.2 组合关系
给 7 种 MergeTree 加上 Replicated 前缀,又能组合出 7 种支持副本协同的表引擎。
ReplicatedMergeTree与普通的MergeTree有什么区别呢?
上图中的虚线框部分是MergeTree的能力边界,而ReplicatedMergeTree在MergeTree能力的基础之上增加了分布式协同的能力,其借助ZooKeeper的消息日志广播功能,实现了副本实例之间的数据同步功能。
1.8 小结
MergeTree 表引擎系列:
| 引擎 | 特性 |
|---|---|
| MergeTree | 基础引擎 + TTL + 多路径存储 |
| ReplacingMergeTree | 数据去重(按 ORDER BY,分区内有效) |
| SummingMergeTree | 自动聚合 SUM |
| AggregatingMergeTree | 预聚合计算(配合物化视图) |
| CollapsingMergeTree | 以增代删(对写入顺序有要求) |
| VersionedCollapsingMergeTree | 以增代删(支持任意写入顺序) |
| Replicated* | 支持副本协同 |