开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情 |
文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》
70.【数据库】ClickHouse从入门到放弃-MergeTree的创建方式 - 掘金 (juejin.cn)
71.【数据库】ClickHouse从入门到放弃-MergeTree的存储结构 - 掘金 (juejin.cn)
72.【数据库】ClickHouse从入门到放弃-数据分区 - 掘金 (juejin.cn)
73.【数据库】ClickHouse从入门到放弃-一级索引 - 掘金 (juejin.cn)
74.【数据库】ClickHouse从入门到放弃-二级索引 - 掘金 (juejin.cn)
75.【数据库】ClickHouse从入门到放弃-数据存储 - 掘金 (juejin.cn)
76.【数据库】ClickHouse从入门到放弃-数据标记 - 掘金 (juejin.cn)
77.【数据库】ClickHouse从入门到放弃-对于分区、索引、标记和压缩数据的协同总结 - 掘金 (juejin.cn)
78.【数据库】ClickHouse从入门到放弃-MergeTree 数据TTL - 掘金 (juejin.cn)
79.【数据库】ClickHouse从入门到放弃-MergeTree 多路径存储策略 - 掘金 (juejin.cn)
1.ReplacingMergeTree
虽然MergeTree拥有主键,但是它的主键却没有唯一键的约束。这意味着即便多行数据的主键相同,它们还是能够被正常写入。 在某些使用场合,用户并不希望数据表中含有重复的数据。ReplacingMergeTree就是在这种背景下为了数据去重而设计的,它能够在合并分区时删除重复的数据。它的出现,确实也在一定程度上解决了重复数据的问题。为什么说是“一定程度”?此处先按下不表。
创建一张ReplacingMergeTree表的方法与创建普通MergeTree表无异,只需要替换Engine:
ENGINE = ReplacingMergeTree(ver)
其中,ver是选填参数,会指定一个UInt*、Date或者DateTime类型的字段作为版本号。这个参数决定了数据去重时所使用的算法。
接下来,用一个具体的示例说明它的用法。首先执行下面的语句创建数据表:
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
注意这里的ORDER BY是去除重复数据的关键,排序键ORDER BY所声明的表达式是后续作为判断数据是否重复的依据。在这个例子中,数据会基于id和code两个字段去重。假设此时表内的测试数据如下:
┌─id───┬─code─┬───────create_time─┐
│ A001 │ C1 │ 2019-05-10 17:00:00 │
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A002 │ C2 │ 2019-05-14 17:00:00 │
│ A003 │ C3 │ 2019-05-15 17:00:00 │
└─────┴─────┴───────────────┘
那么在执行optimize强制触发合并后,会按照id和code分组,保留分组内的最后一条(观察create_time日期字段):
optimize TABLE replace_table FINAL
将其余重复的数据删除:
┌─id───┬─code─┬──────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A002 │ C2 │ 2019-05-14 17:00:00 │
│ A003 │ C3 │ 2019-05-15 17:00:00 │
└────┴────┴──────────────┘
从执行的结果来看,ReplacingMergeTree在去除重复数据时,确实是以ORDER BY排序键为基准的,而不是PRIMARY KEY。因为在上面的例子中,ORDER BY是(id,code),而PRIMARY KEY是id,如果按照id值去除重复数据,则最终结果应该只剩下A001、A002和A003三行数据。
到目前为止,ReplacingMergeTree看起来完美地解决了重复数据的问题。事实果真如此吗?现在尝试写入一批新数据:
INSERT INTO TABLE replace_table VALUES('A001','C1','2019-08-10 17:00:00')
写入之后,执行optimize强制分区合并,并查询数据:
┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-08-22 17:00:00 │
└─────┴────┴─────────────────┘
┌─id──┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A002 │ C2 │ 2019-05-14 17:00:00 │
│ A003 │ C3 │ 2019-05-15 17:00:00 │
└────┴──────┴─────────────────┘
再次观察返回的数据,可以看到A001:C1依然出现了重复。这是怎么回事呢?这是因为ReplacingMergeTree是以分区为单位删除重复数据的。只有在相同的数据分区内重复的数据才可以被删除,而不同数据分区之间的重复数据依然不能被剔除。 这就是上面说ReplacingMergeTree只是在一定程度上解决了重复数据问题的原因。
现在接着说明ReplacingMergeTree版本号的用法。以下面的语句为例:
CREATE TABLE replace_table_v(
id String,
code String,
create_time DateTime
)ENGINE = ReplacingMergeTree(create_time)
PARTITION BY toYYYYMM(create_time)
ORDER BY id
replace_table_v基于id字段去重,并且使用create_time字段作为版本号,假设表内的数据如下所示:
┌─id──┬─code──┬───────────create_time─┐
│ A001 │ C1 │ 2019-05-10 17:00:00 │
│ A001 │ C1 │ 2019-05-25 17:00:00 │
│ A001 │ C1 │ 2019-05-13 17:00:00 │
└────┴─────┴───────────────────┘
那么在删除重复数据的时候,会保留同一组数据内create_time时间最长的那一行:
┌─id────┬─code─┬──────────create_time─┐
│ A001 │ C1 │ 2019-05-25 17:00:00 │
└──────┴────┴──────────────────┘
在知道了ReplacingMergeTree的使用方法后,现在简单梳理一下它的处理逻辑。
(1)使用ORBER BY排序键作为判断重复数据的唯一键。
(2)只有在合并分区的时候才会触发删除重复数据的逻辑。
(3)以数据分区为单位删除重复数据。当分区合并时,同一分区内的重复数据会被删除;不同分区之间的重复数据不会被删除。
(4)在进行数据去重时,因为分区内的数据已经基于ORBER BY进行了排序,所以能够找到那些相邻的重复数据。
(5)数据去重策略有两种:
·如果没有设置ver版本号,则保留同一组重复数据中的最后一行。
·如果设置了ver版本号,则保留同一组重复数据中ver字段取值最大的那一行。