这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
数据库的表引擎决定了一张表的展现形式。我们就拿 InnoDB
来说,它的索引结构也就决定了数据的组织排列,数据的加载,支持事务的情况。
ClickHouse
拥有很庞大表引擎体系。在众多的家族中,MergeTree
在生产环境中使用场景最广。因为只有它支持 主键索引,数据分区,数据副本 这些特性。
因为 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 内,数据和一级索引都以相同的规则升序排列。
然后我们来看看他的物理存储结构:
primary.idx
存放稀疏索引,在查询的时候可以排除主键之外的数据,减少无效扫描加速查询。- 若分区表达式:
Partition BY toYYYYMM(create_time)
。那么partition.dat
为计算后的值2020-06
,minmax_create_time.idx
存放是2020-06-06, 2020-06-07
。 - 在分区index的作用下,查询时可以跳过那些不必要的数据分区。
数据分区
在MergeTree
中,数据是以分区的形式进行组织的。这个和数据分片不一样,分片是指一张表的数据分布到不同的数据库节点上。数据分区是数据的纵向切分,数据分片是横向扩展。
MergeTree
数据分区的规则由分区ID决定,也就是分区键的key值决定的。这个和建表的时候指定的 Partition BY key
有关,下面来说明一下分区ID的生成逻辑规则:
分区键类型 | data case | partition express | 分区ID |
---|---|---|---|
无分区键 | all | ||
整型 | 7,8,9 | Partition By day_num | 7;8;9 |
'java','py','go' | Partition By length(lang) | 2;4 | |
日期 | 2020-06-01,2020-06-08 | Partition By create_day | 20200601;20200608 |
2020-05-01,2020-06-08 | Partition By toYYYYMM(createday) | 202005;202006 | |
其他 | 'clickhouse' | Partition By lang | hash(lang)会算出hash值作为分区ID |
分区合并
- 首先只有当插入数据的时候才会创建新的分区。
- 创建的分区也不是一成不变的,会在一定时间之后做自动合并(写入的 10-15 分钟)。
- 每一次
Insert
都会生成新的分区数据。这个不像其他数据库,会自动 insert 到相同的分区。也就是说,对于相同分区,也会生成不同的分区目录。 - 合并分区之后的旧分区不会立即删除。旧的分区会被设置
active=0
,查询的时候会过滤掉这些分区记录,并会在一定时间之后自动删除(默认 8 分钟)。
从上图 分区合并 可以看出:
MinBlock/MaxBlock
在合并的时候分别取同一分区的Min/Max
值;Level
在每一次合并++
;