李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
大数据
正文
11.ClickHouse之数据一致性
Leefs
2022-05-05 PM
2152℃
0条
[TOC] ### 一、概述 查询 CK 手册发现,即便对数据一致性支持最好的 Mergetree,也只是**保证最终一致性**: ![11.ClickHouse之数据一致性01.png](https://lilinchao.com/usr/uploads/2022/05/1202152054.png) 我们在使用 **ReplacingMergeTree**、**SummingMergeTree** 这类表引擎的时候,会出现短暂数据不一致的情况。 在某些对一致性非常敏感的场景,通常有以下几种解决方案。 ### 二、准备测试表和数据 **(1)创建表** ```sql CREATE TABLE test_a( user_id UInt64, score String, deleted UInt8 DEFAULT 0, create_time DateTime DEFAULT toDateTime(0) )ENGINE=ReplacingMergeTree(create_time) ORDER BY user_id; ``` **字段说明:** + **user_id**:数据去重更新的标识; + **create_time**:版本号字段,每组数据中 create_time 最大的一行表示最新的数据; + **deleted**:自定的一个标记位,比如 0 代表未删除,1 代表删除数据。 **(2)写入1000 万 测试数据** ```sql INSERT INTO TABLE 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); ``` + **查询数据总数** ![11.ClickHouse之数据一致性02.jpg](https://lilinchao.com/usr/uploads/2022/05/1625897431.jpg) **(3)修改前50万行数据,修改内容包括 name 字段和 create_time 版本号字段** ```sql INSERT INTO TABLE test_a(user_id,score,create_time) WITH( SELECT ['AA','BB','CC','DD','EE','FF','GG'] )AS dict SELECT number AS user_id, dict[number%7+1], now() AS create_time FROM numbers(500000); ``` + **再次查询数据总数** ![11.ClickHouse之数据一致性03.jpg](https://lilinchao.com/usr/uploads/2022/05/3455981843.jpg) 从两次总数查询结果可以看出,执行完修改操作后,还未触发分区合并,所以还未去重。 ### 二、手动OPTIMIZE #### 手动执行合并操作 在写入数据后,立刻执行 **OPTIMIZE** 强制触发新写入分区的合并动作。 ```sql OPTIMIZE TABLE test_a FINAL; // 语法:OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | PARTITION ID 'partition_id'] [FINAL] [DEDUPLICATE [BY expression]] ``` **语法说明:** + **[ON CLUSTER cluster]**:整个集群合并; + **[DEDUPLICATE [BY expression]]**:根据那些字段进行合并,如果指定 DEDUPLICATE,那么完全相同的行(除非指定了 by-clause)将被删除重复(所有列都被比较),这仅对 MergeTree 引擎有意义。 + **[FINAL]**:如果指定 FINAL,即使所有数据都已在一个部分中,也会执行优化。即使执行并发合并,也会强制合并。 如果 OPTIMIZE 由于任何原因没有执行合并,它不会通知客户端。要启用通知,请使用 optimize_throw_if_noop 设置。 #### 查询结果 ![11.ClickHouse之数据一致性04.jpg](https://lilinchao.com/usr/uploads/2022/05/3842645227.jpg) ### 三、通过Group by去重 > **去重目标** > > 对因数据修改或删除造成的重复数据通过SQL在查询时进行过滤 **(1)执行去重查询** ```sql SELECT user_id, argMax(score, create_time) AS score, argMax(deleted, create_time) AS deleted, max(create_time) AS ctime FROM test_a GROUP BY user_id HAVING deleted = 0; ``` **函数说明:** + **argMax(field1,field2)**:按照 field2 的最大值取 field1 的值。 当更新数据时 , 会写入一行新的数据 , 例如上面语句中 , 通过查询最大的create_time 得到修改后的 score 字段值。 *注意:在执行上方查询语法时,将虚拟机内存调至3G以上,不然可能会出现内存不足问题。* **(2)创建视图,方便测试** ```sql CREATE VIEW view_test_a AS SELECT user_id , argMax(score, create_time) AS score, argMax(deleted, create_time) AS deleted, max(create_time) AS ctime FROM test_a GROUP BY user_id HAVING deleted = 0; ``` **(3)插入重复数据,再次查询** ```sql -- 再次插入一条数据 INSERT INTO TABLE test_a(user_id,score,create_time) VALUES(0,'AAAA',now()) -- 再次查询 SELECT * FROM view_test_a WHERE user_id = 0; ``` ![11.ClickHouse之数据一致性05.jpg](https://lilinchao.com/usr/uploads/2022/05/4135810648.jpg) 在查询视图中,只查询出来了最新一条数据。 **(4)删除数据测试** ```sql -- 再次插入一条标记为删除的数据 INSERT INTO TABLE test_a(user_id,score,deleted,create_time) VALUES(0,'AAAA',1,now()); -- 再次查询,刚才那条数据看不到了 SELECT * FROM view_test_a WHERE user_id = 0; ``` ![11.ClickHouse之数据一致性06.jpg](https://lilinchao.com/usr/uploads/2022/05/2792379917.jpg) 这行数据并没有被真正的删除,而是被过滤掉了。在一些合适的场景下,可以结合表级别的 TTL 最终将物理数据删除。 ### 四、通过FINAL查询 在查询语句后增加 FINAL 修饰符,这样在查询的过程中将会执行 Merge 的特殊逻辑(例如数据去重,预聚合等)。 但是这种方法在早期版本基本没有人使用,因为在增加 FINAL 之后,我们的查询将会变 成一个单线程的执行过程,查询速度非常慢。 在 **v20.5.2.7-stable** 版本中,FINAL 查询支持多线程执行,并且可以通过 **max_final_threads** 参数控制单个查询的线程数。但是目前读取 part 部分的动作依然是串行的。 FINAL 查询最终的性能和很多因素相关,列字段的大小、分区的数量等等都会影响到最终的查询时间,所以还要结合实际场景取舍。 **使用 hits_v1 表进行测试:** 分别安装了 20.4.5.36 和 21.7.3.14 两个版本的 ClickHouse 进行对比。也可以到官网去测试。 #### 4.1 老版本测试 **(1)普通查询语句** ```sql select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100; ``` **(2)FINAL 查询** ```sql select * from visits_v1 FINAL WHERE StartDate = '2014-03-17' limit 100; ``` 先前的并行查询变成了单线程。 #### 4.2 新版本测试 **(1)普通语句查询** ```sql select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100 settings max_threads = 2; ``` + **查看执行计划** ```sql EXPLAIN PIPELINE SELECT * FROM visits_v1 WHERE StartDate = '2014-03-17' LIMIT 100 SETTINGS max_threads = 2 ┌─explain─────────────────────────┐ │ (Expression) │ │ ExpressionTransform × 2 │ │ (SettingQuotaAndLimits) │ │ (Limit) │ │ Limit 2 → 2 │ │ (ReadFromMergeTree) │ │ MergeTreeThread × 2 0 → 1 │ └─────────────────────────────────┘ ``` 明显将由 2 个线程并行读取 part 查询 **(2)FINAL 查询** ```sql select * from visits_v1 final WHERE StartDate = '2014-03-17' limit 100 settings max_final_threads = 2; ``` 查询速度没有普通的查询快,但是相比之前已经有了一些提升,查看 FINAL 查询的执行计划: ```sql EXPLAIN PIPELINE SELECT * FROM visits_v1 FINAL WHERE StartDate = '2014-03-17' LIMIT 100 SETTINGS max_final_threads = 2 ┌─explain───────────────────────────────┐ │ (Expression) │ │ ExpressionTransform × 2 │ │ (SettingQuotaAndLimits) │ │ (Limit) │ │ Limit 2 → 2 │ │ (ReadFromMergeTree) │ │ ExpressionTransform × 2 │ │ CollapsingSortedTransform × 2 │ │ Copy 1 → 2 │ │ AddingSelector │ │ ExpressionTransform │ │ MergeTree 0 → 1 │ └───────────────────────────────────────┘ ``` 从 **CollapsingSortedTransform** 这一步开始已经是多线程执行,但是读取分区数据的部分的动作还是串行。 ### 总结 replacingMergeTree不能保证查询时没有重复,只能保证最终的一致性; **解决方法:** 1. 通过手动执行合并操作来进行去重,但是在生产环境中不推荐使用。 2. 通过SQL查询去重,进行group by分组操作和增加标记字段。 3. 使用final关键字,如果是20.5之前的版本final是单线程;20.5之后是多线程,但是读取分区数据的时候是串行;
标签:
ClickHouse
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2067.html
上一篇
10.ClickHouse建表优化
下一篇
12.ClickHouse之物化视图
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
Netty
Linux
Azkaban
Eclipse
FileBeat
容器深入研究
稀疏数组
FastDFS
JavaScript
Yarn
Typora
Nacos
国产数据库改造
排序
锁
DataWarehouse
SQL练习题
SpringCloud
Java工具类
ClickHouse
JavaWEB项目搭建
Sentinel
Git
数据结构
递归
Spark
GET和POST
Kibana
Redis
pytorch
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭