当前位置: 首页 > news >正文

ClickHouse数据一致性

查询CK手册发现,即便对数据一致性支持最好的Mergetree,也只是保证最终一致性

我们在使用 ReplacingMergeTree、SummingMergeTree 这类表引擎的时候,会出现短暂数据不一致的情况。

在某些对一致性非常敏感的场景,通常有以下几种解决方案。

准备测试表和数据

(1)创建表

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万 测试数据

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);

(3)修改前 50万 行数据,修改内容包括 name 字段和 create_time 版本号字段

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);

(4)统计总数

SELECT COUNT() FROM test_a;10500000

还未触发分区合并,所以还未去重。

手动 OPTIMIZE

在写入数据后,立刻执行OPTIMIZE强制触发新写入分区的合并动作。

OPTIMIZE TABLE test_a FINAL;语法:OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | PARTITION ID 'partition_id'] [FINAL] [DEDUPLICATE [BY expression]]

通过 Group by 去重

(1)执行去重的查询

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字段值。

(2)创建视图,方便测试

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)插入重复数据,再次查询

#再次插入一条数据
INSERT INTO TABLE test_a(user_id,score,create_time) VALUES(0,'AAAA',now())#再次查询
SELECT *
FROM view_test_a
WHERE user_id = 0;

4)删除数据测试

#再次插入一条标记为删除的数据
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;

这行数据并没有被真正的删除,而是被过滤掉了。在一些合适的场景下,可以结合 表级别的 TTL 最终将物理数据删除。

通过 FINAL 查询

在查询语句后增加FINAL修饰符这样在查询的过程中将会执行Merge的特殊逻辑例如数据去重预聚合等

但是这种方法在早期版本基本没有人使用,因为在增加 FINAL之后,我们的查询将会变成一个单线程的执行过程,查询速度非常慢。

v20.5.2.7-stable版本中,FINAL查询支持多线程执行,并且可以通过max_final_threads 参数控制单个查询的线程数。但是目前读取part部分的动作依然是串行的。

FINAL查询最终的性能和很多因素相关,列字段的大小、分区的数量等等都会影响到最终的查询时间,所以还要结合实际场景取舍。

参考链接:https://github.com/ClickHouse/ClickHouse/pull/10463

使用hits_v1表进行测试:

分别安装了20.4.5.36  21.7.3.14 两个版本的ClickHouse进行对比。

4.1 老版本测试

1普通查询语句

select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100;

2)FINAL查询

select * from visits_v1 FINAL WHERE StartDate = '2014-03-17' limit 100;

先前的并行查询变成了单线程。

4.2 新版本测试

1)普通语句查询

select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100 settings max_threads = 2;

查看执行计划

explain pipeline select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100 settings max_threads = 2;

(Expression)                   

ExpressionTransform × 2        

  (SettingQuotaAndLimits)      

    (Limit)                    

    Limit 2 → 2

      (ReadFromMergeTree)      

MergeTreeThread × 2 0 → 1

明显将由2个线程并行读取 part 查询。

2)FINAL查询

select * from visits_vfinal WHERE StartDate = '2014-03-17' limit 100  settings max_final_threads = 2;

查询速度没有普通的查询快,但是相比之前已经有了一些提升,查看 FINAL 查询的执行计划:

explain pipeline select * from visits_v1 final WHERE StartDate = '2014-03-17' limit 100  settings max_final_threads = 2;

(Expression)                          

ExpressionTransform × 2               

  (SettingQuotaAndLimits)             

    (Limit)                           

    Limit 2 → 2                       

      (ReadFromMergeTree)             

      ExpressionTransform × 2         

CollapsingSortedTransform × 2

          Copy 1 → 2                  

            AddingSelector            

ExpressionTransform

                MergeTree 0 → 1       

CollapsingSortedTransform这一步开始已经是多线程执行,但是读取 part 部分的动作还是串行。

相关文章:

  • LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字
  • 代码随想录算法训练营第二十八天| 78 子集 90 子集|| 93 复原IP地址
  • 基于SSM的北海旅游网站设计与实现
  • Windows安装Vmware 虚拟机
  • Axios传值的几种方式
  • 左支座零件的机械加工工艺规程及工艺装备设计【计算机辅助设计与制造CAD】
  • YOLOv8 加持 MobileNetv3,目标检测新篇章
  • docker的基本使用以及使用Docker 运行D435i
  • Notepad+正则表达式使用方法
  • SpringCloud 微服务全栈体系(十四)
  • Android 13 - Media框架(14)- OpenMax(二)
  • python django 小程序图书借阅源码
  • C#入门(2): namespace、类
  • 计算机毕业设计选题推荐-点餐微信小程序/安卓APP-项目实战
  • 蓝桥杯单片机综合练习——工厂灯光控制
  • 【前端学习】-粗谈选择器
  • JavaScript 基本功--面试宝典
  • ng6--错误信息小结(持续更新)
  • Redis 中的布隆过滤器
  • 阿里云购买磁盘后挂载
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 对JS继承的一点思考
  • 多线程 start 和 run 方法到底有什么区别?
  • 飞驰在Mesos的涡轮引擎上
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 技术:超级实用的电脑小技巧
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 突破自己的技术思维
  • 问题之ssh中Host key verification failed的解决
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 移动端 h5开发相关内容总结(三)
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 在weex里面使用chart图表
  • Spring Batch JSON 支持
  • 湖北分布式智能数据采集方法有哪些?
  • ​比特币大跌的 2 个原因
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • $jQuery 重写Alert样式方法
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)ORM
  • (转)程序员疫苗:代码注入
  • .NET 反射的使用
  • .NET 简介:跨平台、开源、高性能的开发平台
  • :not(:first-child)和:not(:last-child)的用法
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • @Transactional 竟也能解决分布式事务?
  • @基于大模型的旅游路线推荐方案
  • [ vulhub漏洞复现篇 ] Apache APISIX 默认密钥漏洞 CVE-2020-13945