开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情
文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》
80.【数据库】ClickHouse从入门到放弃-ReplacingMergeTree - 掘金 (juejin.cn)
81.【数据库】ClickHouse从入门到放弃-SummingMergeTree - 掘金 (juejin.cn)
82.【数据库】ClickHouse从入门到放弃-AggregatingMergeTree - 掘金 (juejin.cn)
书接前几回的文章,这次继续介绍MergeTree中的CollapsingMergeTree
CollapsingMergeTree
假设现在需要设计一款数据库,该数据库支持对已经存在的数据实现行级粒度的修改或删除,你会怎么设计?一种最符合常理的思维可能是:首先找到保存数据的文件,接着修改这个文件,删除或者修改那些需要变化的数据行。然而在大数据领域,对于ClickHouse这类高性能分析型数据库而言,对数据源文件修改是一件非常奢侈且代价高昂的操作。相较于直接修改源文件,它们会将修改和删除操作转换成新增操作,即以增代删。
CollapsingMergeTree就是一种通过以增代删的思路,支持行级数据修改和删除的表引擎。它通过定义一个sign标记位字段,记录数据行的状态。如果sign标记为1,则表示这是一行有效的数据;如果sign标记为-1,则表示这行数据需要被删除。当CollapsingMergeTree分区合并时,同一数据分区内,sign标记为1和-1的一组数据会被抵消删除。这种1和-1相互抵消的操作,犹如将一张瓦楞纸折叠了一般。这种直观的比喻,想必也正是折叠合并树(CollapsingMergeTree)名称的由来,其折叠的过程如图7-2所示。
声明CollapsingMergeTree的方式如下:
ENGINE = CollapsingMergeTree(sign)
其中,sign用于指定一个Int8类型的标志位字段。一个完整的使用示例如下所示:
CREATE TABLE collpase_table(
id String,
code Int32,
create_time DateTime,
sign Int8
)ENGINE = CollapsingMergeTree(sign)
PARTITION BY toYYYYMM(create_time)
ORDER BY id
与其他的MergeTree变种引擎一样,CollapsingMergeTree同样是以ORDER BY排序键作为后续判断数据唯一性的依据。按照之前的介绍,对于上述collpase_table数据表而言,除了常规的新增数据操作之外,还能够支持两种操作。
其一,修改一行数据: --修改前的源数据, 它需要被修改 INSERT INTO TABLE collpase_table VALUES('A000',100,'2019-02-20 00:00:00',1)
--镜像数据, ORDER BY字段与源数据相同(其他字段可以不同),sign取反为-1,它会和源数据折叠 INSERT INTO TABLE collpase_table VALUES('A000',100,'2019-02-20 00:00:00',-1)
--修改后的数据 ,sign为1 INSERT INTO TABLE collpase_table VALUES('A000',120,'2019-02-20 00:00:00',1)
其二,删除一行数据:
--修改前的源数据, 它需要被删除
INSERT INTO TABLE collpase_table VALUES('A000',100,'2019-02-20 00:00:00',1)
--镜像数据, ORDER BY字段与源数据相同, sign取反为-1, 它会和源数据折叠
INSERT INTO TABLE collpase_table VALUES('A000',100,'2019-02-20 00:00:00',-1)
CollapsingMergeTree在折叠数据时,遵循以下规则。
·如果sign=1比sign=-1的数据多一行,则保留最后一行sign=1的数据。
·如果sign=-1比sign=1的数据多一行,则保留第一行sign=-1的数据。
·如果sign=1和sign=-1的数据行一样多,并且最后一行是sign=1,则保留第一行sign=-1和最后一行sign=1的数据。
·如果sign=1和sign=-1的数据行一样多,并且最后一行是sign=-1,则什么也不保留。
·其余情况,ClickHouse会打印警告日志,但不会报错,在这种情形下,查询结果不可预知。
在使用CollapsingMergeTree的时候,还有几点需要注意。
(1)折叠数据并不是实时触发的,和所有其他的MergeTree变种表引擎一样,这项特性也只有在分区合并的时候才会体现。所以在分区合并之前,用户还是会看到旧的数据。解决这个问题的方式有两种。
·在查询数据之前,使用optimize TABLE table_name FINAL命令强制分区合并,但是这种方法效率极低,在实际生产环境中慎用。
·需要改变我们的查询方式。以collpase_table举例,如果原始的SQL如下所示:
SELECT id,SUM(code),COUNT(code),AVG(code),uniq(code)
FROM collpase_table
GROUP BY id
则需要改写成如下形式:
SELECT id,SUM(code * sign),COUNT(code * sign),AVG(code * sign),uniq(code * sign)
FROM collpase_table
GROUP BY id
HAVING SUM(sign) > 0
(2)**只有相同分区内的数据才有可能被折叠。**不过这项限制对于CollapsingMergeTree来说通常不是问题,因为修改或者删除数据的时候,这些数据的分区规则通常都是一致的,并不会改变。
(3)最后这项限制可能是CollapsingMergeTree最大的命门所在。CollapsingMergeTree对于写入数据的顺序有着严格要求。现在用一个示例说明。如果按照正常顺序写入,先写入sign=1,再写入sign=-1,则能够正常折叠:
--先写入sign=1
INSERT INTO TABLE collpase_table VALUES('A000',102,'2019-02-20 00:00:00',1)
--再写入sign=-1
INSERT INTO TABLE collpase_table VALUES('A000',101,'2019-02-20 00:00:00',-1)
现在将写入的顺序置换,先写入sign=-1,再写入sign=1,则不能够折叠:
--先写入sign=-1
INSERT INTO TABLE collpase_table VALUES('A000',101,'2019-02-20 00:00:00',-1)
--再写入sign=1
INSERT INTO TABLE collpase_table VALUES('A000',102,'2019-02-20 00:00:00',1)
这种现象是CollapsingMergeTree的处理机制引起的,因为它要求sign=1和sign=-1的数据相邻。而分区内的数据基于ORBER BY排序,要实现sign=1和sign=-1的数据相邻,则只能依靠严格按照顺序写入。
如果数据的写入程序是单线程执行的,则能够较好地控制写入顺序;如果需要处理的数据量很大,数据的写入程序通常是多线程执行的,那么此时就不能保障数据的写入顺序了。在这种情况下,CollapsingMergeTree的工作机制就会出现问题。为了解决这个问题,ClickHouse另外提供了一个名为VersionedCollapsingMergeTree的表引擎,明天继续分析它