ClickHouse的MergeTree表引擎

上一篇文章介绍了ClickHouse表的引擎,内容比较少。也比较好消化,而ClickHouse中最强大的为MergeTree(合并树)引擎系列(*MergeTree)中的其他引擎。当有非常大的数据量插入到表中,要高效地一批批写入数据片段,并希望这些数据片段在后台按照一定规则合并。相比在插入时不断修改数据进行存储,MergeTree引擎会高效很多。

MergeTree的创建方式与存储结构

建表sql以及插入sql

1
2
3
4
5
6
-- 建表sql
create table demo1_table(date Date,id UInt8,name String) engine = MergeTree partition by toYYYYMM(date) order by id;
-- 插入sql
insert into demo1_table values ('2021-06-29', 1, 'demo1');
insert into demo1_table values ('2021-06-30', 2, 'demo2');
insert into demo1_table values ('2021-07-01', 3, 'demo3');

运行以上sql成功后可以在cd /var/lib/clickhouse/data/default/demo1_table/目录中看到如下内容

1
202106_1_1_0  202106_2_2_0  202107_3_3_0  detached  format_version.txt

MergeTree的存储结构:随便进入一个目录,例如202107_3_3_0,在该目录下会看到如下文件
checksums.txt、columns.txt、count.txt、date.bin、date.mrk2、id.bin、id.mrk2、minmax_date.idx、name.bin、name.mrk2、partition.dat、primary.idx

  1. checksums.txt:二进制的校验文件,保存了余下文件的大小size和size的Hash值,用于快速校验文件的完整和正确性
  2. columns.txt:明文的列信息文件
  3. *.bin:压缩格式(默认LZ4)的数据文件,保存了原始数据。以列名.bin命名
  4. *.mrk2:mrk2文件存储的是bin文件中数据位置的映射关系
  5. primary.idx:二进制的一级索引文件,在建表的时候通过OrderBy或者PrimaryKey声明的稀疏索引

数据分区

数据是以分区目录的形式组织的,每个分区独立分开存储。查询数据时,可以有效的跳过无用的数据文件。数据的分区规则如下:
分区键的取值,生成分区ID,分区根据ID决定。根据分区键的数据类型不同,分区ID的生成目前有四种规则:

  1. 不指定分区键
  2. 使用整形
  3. 使用日期类型 toYYYYMM(date)
  4. 使用其他类型

在数据写入时,会对照分区ID落入对应的分区。例如partitionID_MinBlockNum_MaxBlockNum_Level
BlockNum是一个全局整型,从1开始,每当新创建一个分区目录,此数字就累加1;MinBlockNum是最小数据块编号;MaxBlockNum是最大数据块编号。
对于一个新的分区,MinBlockNum和MaxBlockNum的值相同。Level则代表合并的层级,即某个分区被合并过的次数。不是全局的,而是针对某一个分区。
MergeTree的数据分区也有合并过程。不同的批次写入数据属于同一分区,也会生成不同的目录,在之后的某个时刻再合并(写入后的10~15分钟),合并后的旧分区目录默认8分钟后删除。
同一分区的多个目录合并以后的命名规则如下:

  1. MinBlockNum:取同一分区中MinBlockNum值最小的
  2. MaxBlockNum:取同一分区中MaxBlockNum值最大的
  3. Level:取同一分区最大的Level值加1

索引

索引的文件为:primary.idx
MergeTree的主键使用Primary Key定义,主键定义之后,MergeTree会根据index_granularity间隔(默认8192)为数据生成一级索引并保存至primary.idx文件中。这种方式称为稀疏索引。
也可以通过通过order by指代主键。稀疏索引:每一行索引标记对应一段数据记录(默认索引粒度为8192);稠密索引:每一行索引标记对应一行具体的数据记录。稀疏索引占用空间小,取用速度快。

MergeTree的TTL

TTL表示数据的存活时间,即可以设置在表上,也可以设置在列上。TTL指定的时间到期后则删除相应的表或列,如果同时设置了TTL,则先删除过期时间相应数据。TTL设置在列上如下所示

1
2
3
4
5
6
7
8
9
10
11
12
-- 建表语句
create table ttl_table (
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;
-- 插入语句
insert into ttl_table values('A1',now(),'C1',1),('A3',now()+INTERVAL 10 MINUTE,'C1',1);

TTL设置在表上如下所示

1
2
3
4
5
6
7
8
9
10
create table tt1_table_v2 (
id String,
create_time DateTime,
code String TTL create_time + INTERVAL 10 SECOND,
type UInt8
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY create_time
TTL create_time + INTERVAL 1 DAY;