,下面我们一起去解密~~~
CREATE TABLE test_z(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 代表删除数据。
INSERT INTO TABLE test_z(user_id,score)WITH(SELECT ['A','B','C','D','E','F','G'])AS dictSELECT number AS user_id, dict[number%7+1] FROM numbers(10000000);
INSERT INTO TABLE test_z(user_id,score,create_time)WITH(SELECT ['AA','BB','CC','DD','EE','FF','GG'])AS dictSELECT number AS user_id, dict[number%7+1], now() AS create_time FROMnumbers(500000);
SELECT COUNT() FROM test_z;10500000
OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | PARTITION ID 'partition_id'] [FINAL | FORCE] [DEDUPLICATE [BY expression]]
在写入数据后,立刻执行 OPTIMIZE 强制触发新写入分区的合并动作。
OPTIMIZE TABLE test_z FINAL;
@Intercepts({@org.apache.ibatis.plugin.Signature(type = StatementHandler.class, method = "query", args = { Statement.class })})public class MyCustomInterceptor implements org.apache.ibatis.plugin.Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {/ 执行查询逻辑Object result = invocation.proceed();/ 在查询响应后执行你自定义的逻辑/ 例如:你可以在这里执行一些额外的 SQL 语句System.out.println("OPTIMIZE TABLE test_z FINAL;");/ 在这里你可以选择执行一些 SQL,例如更新日志、统计等return result;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}}
MybatisPlusInterceptor中
@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加自定义拦截器interceptor.addInnerInterceptor(new MyCustomInterceptor());return interceptor;}
SELECTuser_id ,argMax(score, create_time) AS score,argMax(deleted, create_time) AS deleted,max(create_time) AS ctimeFROM test_aGROUP BY user_idHAVING deleted = 0;
在查询语句后增加 FINAL 修饰符,这样在查询的过程中将会执行 Merge 的特殊逻辑(例如数据去重,预聚合等),它会强制 ClickHouse 在查询时对数据进行最终的合并,以确保返回的结果是最终一致的。
一般涉及到具有强制性的概念,我们都知道肯定要牺牲一些东西去换想要的效果。所以使用FINAL修饰词的代价就是会导致查询性能下降,因为它需要在查询时对所有数据进行额外的排序和合并操作。这种操作通常是单线程的,尤其是在处理大规模数据时,性能可能会成为瓶颈。
普通查询
select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100 settingsmax_threads = 2;
explain执行计划
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 个线程并行读取的查询。
FINAL查询
select * from visits_v1 final WHERE StartDate = '2014-03-17' limit 100settings max_final_threads = 2;
查询速度没有普通的查询快,但是相比之前老版本已经有了很多提升。
explain执行计划
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 × 2CollapsingSortedTransform × 2Copy 1 → 2AddingSelectorExpressionTransformMergeTree 0 → 1
从 CollapsingSortedTransform 这一步开始已经是多线程执行,但是读取具体数据部分的动作还是串行。
总结
目前我使用版本23.3.9.55,如果在开发中需要使用到FINAL,我建议限制语句查询范围(如时间范围、过滤条件),减少需要处理的数据量。
在项目木尽量避免全面使用FINAL,只对数据幂等性要求高的功能点进行使用。 FINAL 以及 语句Group By去重 和 预合并数据( OPTIMIZE
)结合使用效果最好。
文章转载自迷三张,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




