目前在 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 最大的那一行

核心逻辑:

  1. 使用 ORDER BY 排序键作为判断重复数据的唯一键

  2. 只有在合并分区时才触发删除重复数据

  3. 以数据分区为单位删除重复数据(跨分区无法去重)

  4. 无 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)),不指定则汇总所有非主键数值类型字段。

核心逻辑:

  1. ORDER BY 作为聚合 Key

  2. 合并分区时触发汇总

  3. 同一分区内相同聚合 Key 的数据合并为一行

  4. 非汇总字段取第一行数据

  5. 支持嵌套类型,列名需以 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 → 全部删除

注意事项:

  1. 折叠不是实时的,只在分区合并时触发

  2. 查询时需改写:

SELECT id, SUM(code * sign), COUNT(code * sign)
FROM collapse_table
GROUP BY id
HAVING SUM(sign) > 0;
  1. 对写入顺序有严格要求:必须先写 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 动作时调用各自独有的合并逻辑。

image-20260712155046659

1.7.2 组合关系

给 7 种 MergeTree 加上 Replicated 前缀,又能组合出 7 种支持副本协同的表引擎。

image-20260712155405502

ReplicatedMergeTree与普通的MergeTree有什么区别呢?

image-20260712155245272

上图中的虚线框部分是MergeTree的能力边界,而ReplicatedMergeTree在MergeTree能力的基础之上增加了分布式协同的能力,其借助ZooKeeper的消息日志广播功能,实现了副本实例之间的数据同步功能。

1.8 小结

MergeTree 表引擎系列:

引擎特性
MergeTree基础引擎 + TTL + 多路径存储
ReplacingMergeTree数据去重(按 ORDER BY,分区内有效)
SummingMergeTree自动聚合 SUM
AggregatingMergeTree预聚合计算(配合物化视图)
CollapsingMergeTree以增代删(对写入顺序有要求)
VersionedCollapsingMergeTree以增代删(支持任意写入顺序)
Replicated*支持副本协同

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注