• 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    其中:
    user_id 是数据去重更新的标识;
    create_time 是版本号字段,每组数据中 create_time 最大的一行表示最新的数据;
    deleted 是自定的一个标记位,比如 0 代表未删除,1 代表删除数据。
    
    • 1
    • 2
    • 3
    • 4

    (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);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (4)统计总数

    SELECT COUNT() FROM test_a;
    
    10500000
    
    
    • 1
    • 2
    • 3
    • 4

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

    二、手动 OPTIMIZE

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

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

    三、通过 Group by 去重

    (1)执行去重的查询

     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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    函数说明:

    ◼ argMax(field1,field2):按照 field2 的最大值取 field1 的值。
    
    • 1

    当我们更新数据时,会写入一行新的数据,例如上面语句中,通过查询最大的
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (3)插入重复数据,再次查询

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

    (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;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这行数据并没有被真正的删除,而是被过滤掉了。在一些合适的场景下,可以结合 表级别的 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 进行对比。

    1、老版本测试

    (1)普通查询语句

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

    (2)FINAL 查询

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

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

    2、新版本测试

    (1)普通语句查询

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

    查看执行计划:

    explain pipeline select * from visits_v1 WHERE StartDate = '2014-03-17'
    limit 100 settings max_threads = 2;
    
    (Expression) 
    ExpressionTransform × 2 
     (SettingQuotaAndLimits) 
     (Limit) 
     Limit 22 
     (ReadFromMergeTree)
     MergeTreeThread × 2 01
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    (2)FINAL 查询

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

    查询速度没有普通的查询快,但是相比之前已经有了一些提升,查看 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 22 
     (ReadFromMergeTree) 
     ExpressionTransform × 2 
     CollapsingSortedTransform × 2
     Copy 12 
     AddingSelector 
     ExpressionTransform 
     MergeTree 01
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    五、总结

    数据一致性: replacingMergeTree不能保证查询时没重复、只能保证最终一致性
    
    解决方案:
       1、手动执行:生产环境不推荐
       2、通过SQL实现去重:group by ==> 高级一点用法,加标记字段
       3、使用final:
       		20.5 之前的版本、final是单线程
       		20.5 之后的版本,final可以是多线程,但是读取part是串行的
       4、重复一点无所谓:100万日活,统计出来1001
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    Loadrunner结合Fiddler实现脚本的录制
    LeetCode每日一练 —— 876. 链表的中间结点
    TikTok shop美国小店适合哪些人做?附常见运营问题解答
    【排序26:有序矩阵中第 K 小的元素】
    【C语言】单词拼写检查
    node中package解析、npm 命令行npm详解,node中的common模块化,npm、nrm两种方式查看源和切换镜像
    选择商品属性弹框从底部弹出动画效果
    PPT文件不能编辑可以这样解决
    设计模式——抽象工厂模式
    java hashmap 单词计数
  • 原文地址:https://blog.csdn.net/qq_42082701/article/details/127754192