这个问题需要分情况讨论,首先有2个前提:
- MergeTree主键建立以后是不能修改的
- 主键必须和排序键一致或是排序键的前缀,即主键是A,排序键可以是A或(A,B),但不能是B
一、场景模拟
建立test表:
CREATE TABLE default.test
(
`t_date` Date,
`t_device_id` String,
`app_name` String,
`package_name` String,
`launch_time` UInt32
)
ENGINE = MergeTree
PARTITION BY t_date
PRIMARY KEY t_date
ORDER BY t_date
SETTINGS index_granularity = 8192
在前面两个前提下,我们建立4种模拟场景:
1. 主键和排序键一致,均为A,修改排序键为(A,B) -- 达咩
alter table test modify order by (t_date,t_device_id);
2.主键和排序键一致,均为A,新增一列C,修改排序键为(A,C)--OK
alter table test add column country String, modify order by (t_date,country);
3.主键是排序键的前缀,主键为A,排序键为(A,B,C),修改排序键为(A,B) --OK
--先建立排序键为(A,B,C)
alter table test add column version String, modify order by (t_date,country,version);
--删除C,只留下(A,B)
alter table test modify order by (t_date,country)
4.主键是排序键的前缀,主键为A,排序键为(A,B,C),修改排序键为(A,C) -- 达咩
--重新构建排序键为(A,B,C)
alter table test drop column version;
alter table test add column version String, modify order by (t_date,country,version);
--删除中间的B,修改排序键为(A,C)
alter table test modify order by (t_date,version);
结论:MergeTree排序键的修改只能是在原有的基础上添加新增的字段,或者在原有的基础上删除后面的字段。为什么呢?这就需要理解MergeTree索引原理。
二、MergeTree索引原理
MergeTree一级索引由主键定义(前文说过主键和排序键的关系)采用稀疏索引,存储在primary.idx文件中。下图来自clickhouse官网中文版,根据下图我们可以看出,MergeTree会根据排序键(CounterID, Date)对数据进行排序,每隔index_granularity(图中为7,默认是8192)把数据分割成多个小区间,并取出区间的第一个索引标记存入primary.idx文件,与之对齐会建立标记号存入.mrk文件。
MergeTree二级索引由INDEX...GRANULARITY 定义
INDEX index_name expr TYPE type(...) GRANULARITY granularity_value
二级索引采用跳数索引,跳数索引是指数据片段按照粒度(建表时指定的index_granularity)分割成小块后,将上述SQL的granularity_value数量的小块组合成一个大的块,对这些大块写入索引信息,这样有助于使用where筛选时跳过大量不必要的数据,减少SELECT需要读取的数据量。
在Clickhouse中,分区、索引、标记和压缩数据,就好比是MergeTree给出的一套组合拳,共同完成大量数据的高效写入和快速查询。
数据写入:
数据写入的第一步是生成分区目录,伴随着每一批数据的写入,都会生成一个新的分区目录。在后续的某一时刻,属于相同分区的目录会依照规则合并到一起;接着,按照index_granularity索引粒度,会分别生成primary.idx一级索引(如果声明了二级索引,还会创建二级索引文件)、每一个列字段的.mrk数据标记和.bin压缩数据文件。下图所示是一张MergeTre表在写入数据时,它的分区目录、索引、标记和压缩数据的生成过程。
从分区目录201403_1_34_3能够得知,该分区数据共分34批写入,期间发生过3次合并。在数据写入的过程中,依据index_granularity的粒度,依次为每个区间的数据生成索引、标记和压缩数据块。其中,索引和标记区间是对齐的,而标记与压缩块则根据区间数据大小的不同,会生成多对一、一对一和一对多三种关系。
数据查询:
数据查询的本质,可以看作一个不断减小数据范围的过程。在最理想的情况下,MergeTree首先可以依次借助分区索引、一级索引和二级索引,将数据扫描范围缩至最小。然后再借助数据标记,将需要解压与计算的数据范围缩至最小。下图所示为例,它示意了在最优的情况下,经过层层过滤,最终获取最小范围数据的过程。
三、总结(十万个为什么)
1. 为什么MergeTree主键建立以后不能修改?
因为主键索引的建立依赖于排序键,主键索引按照排序顺序建立稀疏索引,如果修改主键,那么意味着需要对所有数据进行重新排序,这对于大数据来说是非常可怕的。
2. 为什么主键必须是排序键或其前缀?
保证即便是主键和排序键不同的情况下,因为主键是排序键的前缀,无论如何不会出现索引和数据顺序混乱的情况。
3. 为什么不能添加已存在的列到排序键中?
跟问题1一样,还是需要对已有数据重新排序的问题,消耗大量资源。
4. 为什么可以添加新增的列到排序键中?
因为新增的列还没有数据,不需要对原有数据的顺序进行重排,只需要等数据插入时,将其插入到适当位置即可。
5. 模拟场景2中改一下,主键和排序键一致,均为A,新增一列C,修改排序键为(C,A)可以吗?
当然不可,总之只要是需要对原有数据进行重排就一定不行!
6. 可以对排序键的记录进行update操作吗?
不行的哦~
四、参考
Clickhouse官网:MergeTree | ClickHouse Documentation
《Clickhouse原理解析与应用实践》--朱凯