80.【数据库】ClickHouse从入门到放弃-ReplacingMergeTree

1,308 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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字段取值最大的那一行。