MergeTree家族枝繁叶茂,人数众多。
但要选个最能打的,ReplacingMergeTree肯定算一个。
Long Long Ago,天地浑浊,ReplacingMergeTree出现了。
起初,它的出现是为了解决重复数据的问题。再往后,利用新版本覆盖旧版本的特性,人们就变相实现了 UPDATE 的功能。
大家一看,INSERT、UPDATE 都有了,那索性再来个 DELETE 吧。于是利用 ReplacingMergeTree 的删除方案就有了,在表上加一个_delete字段,0表示有效,1表示无效。查询的时候把_delete=1的过滤掉。
皆大欢喜,增删改查都齐活了。
But,这种删除方案还是有缺点:
- 查询的时候过滤要自己实现,有点麻烦
- _delete=1的数据并没有被物理删除
时光荏苒,斗转星移,一晃就到2023年了。在 ClickHouse 的新版本中,ReplacingMergeTree 又得到了史诗级加强,居然内置了删除能力。这么一来,你让 CollapsingMergeTree 怎么活呀。
新版本中,ReplacingMergeTree(ver, is_deleted) 多了一个选填参数,
代码语言:javascript复制`is_deleted`:Column data type — `Int8`.
1:删除
0:正常
必须和ver一起使用
接下来举例说明:
创建一张表:
代码语言:javascript复制CREATE TABLE replace_test_a(
user_id UInt64,
score String,
is_deleted UInt8 DEFAULT 0,
create_time DateTime DEFAULT toDateTime(0)
)ENGINE= ReplacingMergeTree(create_time,is_deleted)
ORDER BY user_id
写入1000w数据:
代码语言:javascript复制INSERT INTO TABLE replace_test_a(user_id,score)
WITH(
SELECT ['A','B','C','D','E','F','G']
)AS dict
SELECT number AS user_id, dict[number%7 1] FROM numbers(10000000)
SELECT *
FROM replace_test_a
LIMIT 5
Query id: b023c6e8-6a3f-4419-887a-d024b808c20f
┌─user_id─┬─score─┬─is_deleted─┬─────────create_time─┐
│ 0 │ A │ 0 │ 1970-01-01 08:00:00 │
│ 1 │ B │ 0 │ 1970-01-01 08:00:00 │
│ 2 │ C │ 0 │ 1970-01-01 08:00:00 │
│ 3 │ D │ 0 │ 1970-01-01 08:00:00 │
│ 4 │ E │ 0 │ 1970-01-01 08:00:00 │
└─────────┴───────┴────────────┴─────────────────────┘
修改1行数据, 将 user_id=0的 改成 AAAA:
代码语言:javascript复制INSERT INTO TABLE replace_test_a(user_id,score,create_time) VALUES(0,'AAAA',now())
SELECT *
FROM replace_test_a
FINAL
LIMIT 5
Query id: e01df531-6c81-4ef0-a079-05f78293e4de
┌─user_id─┬─score─┬─is_deleted─┬─────────create_time─┐
│ 0 │ AAAA │ 0 │ 2023-06-29 19:39:27 │
│ 1 │ B │ 0 │ 1970-01-01 08:00:00 │
│ 2 │ C │ 0 │ 1970-01-01 08:00:00 │
│ 3 │ D │ 0 │ 1970-01-01 08:00:00 │
│ 4 │ E │ 0 │ 1970-01-01 08:00:00 │
└─────────┴───────┴────────────┴─────────────────────┘
可以看到,修改成功。
接下来是重头戏,我们删除user_id=0的数据:
代码语言:javascript复制INSERT INTO TABLE replace_test_a(user_id,score,is_deleted,create_time) VALUES(0,'AAAA',1,now())
SELECT *
FROM replace_test_a
FINAL
LIMIT 5
Query id: 2b1b10f7-884d-4dfd-a9a2-8982cb899292
┌─user_id─┬─score─┬─is_deleted─┬─────────create_time─┐
│ 1 │ B │ 0 │ 1970-01-01 08:00:00 │
│ 2 │ C │ 0 │ 1970-01-01 08:00:00 │
│ 3 │ D │ 0 │ 1970-01-01 08:00:00 │
│ 4 │ E │ 0 │ 1970-01-01 08:00:00 │
│ 5 │ F │ 0 │ 1970-01-01 08:00:00 │
└─────────┴───────┴────────────┴─────────────────────┘
删除成功,接下来删除前50w行试试:
代码语言:javascript复制INSERT INTO TABLE replace_test_a(user_id,score,is_deleted,create_time)
WITH(
SELECT ['AA','BB','CC','DD','EE','FF','GG']
)AS dict
SELECT number AS user_id, dict[number%7 1],1, now() AS create_time FROM numbers(500000)
SELECT COUNT()
FROM replace_test_a
FINAL
Query id: 3e738515-b23c-48de-80ca-c90eb35ed6df
┌─count()─┐
│ 9500000 │
数据少了50w行,删除成功。
ReplacingMergeTree的实现原理是:
- 查询时过滤 is_deleted = 1 的数据
- Merge时物理删除is_deleted = 1 的数据
赶快去试试这项新功能吧。
btw,我在例子里的查询都带了FINAL。FINAL的性能在新版本中也得到了加强,以后我会专门写一篇解析的文章。