clickhouse MergeTree「存储+分区初解析」

1,712 阅读4分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

数据库的表引擎决定了一张表的展现形式。我们就拿 InnoDB 来说,它的索引结构也就决定了数据的组织排列,数据的加载,支持事务的情况。

ClickHouse 拥有很庞大表引擎体系。在众多的家族中,MergeTree 在生产环境中使用场景最广。因为只有它支持 主键索引,数据分区,数据副本 这些特性。

image.png

因为 MergeTree 的变种很多,所以很难全部吃透。但是该家族具备了其他表所共有的基本特征。本节从基本的结构进行解读。

存储结构

在使用 MergeTree 写入一批数据时,数据是以 Segment 的形式写入磁盘的,而且这是 immutable。同时为了不然Segment过多,ClickHouse 会通过后台线程,定期合并这些Segment,属于同一个分区的数据Segment会被合成一个新的Segment。

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
    INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
  • PARTITION BY

    分区键。指定表数据以哪个字段进行分区。支持一个字段和多个字段组合成元组。

    如果不声明分区键,则 ClickHouse 会生成一个 all 的大分区。

  • ORDER BY

    排序键。指定数据以哪个字段进行排序。默认和 PRIMARY KEY 一致。支持单个字段和多个字段组合成元组。【排序和 Mysql 中概念一致】。

  • PRIMARY KEY

    主键。声明后会依照主键子段生成index,加速查询。默认情况和 Order By 一致,所以通常也就直接使用排序键的子段为指定主键。

    所以一般情况下,单个 Segment 内,数据和一级索引都以相同的规则升序排列。

然后我们来看看他的物理存储结构:

image.png

  1. primary.idx 存放稀疏索引,在查询的时候可以排除主键之外的数据,减少无效扫描加速查询。
  2. 若分区表达式:Partition BY toYYYYMM(create_time)。那么 partition.dat 为计算后的值 2020-06minmax_create_time.idx 存放是 2020-06-06, 2020-06-07
  3. 在分区index的作用下,查询时可以跳过那些不必要的数据分区。

数据分区

MergeTree 中,数据是以分区的形式进行组织的。这个和数据分片不一样,分片是指一张表的数据分布到不同的数据库节点上。数据分区是数据的纵向切分,数据分片是横向扩展。

MergeTree 数据分区的规则由分区ID决定,也就是分区键的key值决定的。这个和建表的时候指定的 Partition BY key 有关,下面来说明一下分区ID的生成逻辑规则:

分区键类型data casepartition express分区ID
无分区键all
整型7,8,9Partition By day_num 7;8;9
'java','py','go'Partition By length(lang)2;4
日期2020-06-01,2020-06-08Partition By create_day20200601;20200608
2020-05-01,2020-06-08Partition By toYYYYMM(createday)202005;202006
其他'clickhouse'Partition By langhash(lang)会算出hash值作为分区ID

分区合并

  1. 首先只有当插入数据的时候才会创建新的分区。
  2. 创建的分区也不是一成不变的,会在一定时间之后做自动合并(写入的 10-15 分钟)。
  3. 每一次 Insert 都会生成新的分区数据。这个不像其他数据库,会自动 insert 到相同的分区。也就是说,对于相同分区,也会生成不同的分区目录。
  4. 合并分区之后的旧分区不会立即删除。旧的分区会被设置 active=0 ,查询的时候会过滤掉这些分区记录,并会在一定时间之后自动删除(默认 8 分钟)。

image.png

从上图 分区合并 可以看出:

  1. MinBlock/MaxBlock 在合并的时候分别取同一分区的 Min/Max 值;
  2. Level 在每一次合并 ++