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

ClickHouse ReplacingMergeTree 去重陷阱:为什么你的 FINAL 查询无效? - 若

问题背景

在使用 ClickHouse 的 ReplacingMergeTree 引擎时,很多开发者会遇到一个困惑:明明使用了 FINAL 关键字,查询结果却仍然包含重复数据。比如这样的情况:

数据库表
 err := db.Table(model.BlockTaskTableName).Set("gorm:table_options", "ENGINE = ReplacingMergeTree(created_at) PARTITION BY intDiv(start_block, 500000) "+"PRIMARY KEY(start_block, created_at, end_block) "+"ORDER BY (start_block, created_at, end_block, status, owner) "+"SETTINGS enable_block_number_column = 1,enable_block_offset_column = 1").AutoMigrate(&model.BlockTask{})if err != nil {panic(fmt.Sprintf("Failed to create block_tasks table in %s: %v", chain, err))}
 
FInal查询语句
 SELECT * FROM test_ethereum.block_tasks FINAL WHERE status = 'init' AND (owner = '' OR owner IS NULL) ORDER BY start_block DESC, created_at DESC LIMIT 100

 

final查询结果
 created_at                   |updated_at                   |deleted_at|start_block|end_block|status|owner|assigned_at        |completed_at       |data_hash|reset_times|blocks_finished|transactions_finished|logs_finished|acc
-----------------------------+-----------------------------+----------+-----------+---------+------+-----+-------------------+-------------------+---------+-----------+---------------+---------------------+-------------+---
2025-09-30 14:55:47.849000000|2025-09-30 14:55:47.849000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:46.718000000|2025-09-30 14:55:46.718000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:45.544000000|2025-09-30 14:55:45.544000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:44.394000000|2025-09-30 14:55:44.394000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:43.182000000|2025-09-30 14:55:43.182000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:42.029000000|2025-09-30 14:55:42.029000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:40.887000000|2025-09-30 14:55:40.887000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:39.710000000|2025-09-30 14:55:39.710000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0|   
2025-09-30 14:55:38.516000000|2025-09-30 14:55:38.516000000|          |   23379678| 23379687|init  |     |1970-01-01 08:00:00|1970-01-01 08:00:00|         |          0|              0|                    0|            0| 

 

返回的结果中,相同的 start_block 和 end_block 组合出现了多次,完全没有去重效果。

问题根源:ORDER BY 的错误理解

ReplacingMergeTree 的工作原理

ReplacingMergeTree 的去重机制基于两个关键部分:

  1. 版本字段:在引擎声明中指定,如 ReplacingMergeTree(created_at)

  2. 去重依据:由 ORDER BY 子句定义的字段组合

核心规则:当 ORDER BY 的所有字段都相同时,ClickHouse 才认为这些记录是"重复数据",然后根据版本字段保留最新版本。

错误的表结构设计

sql
-- 错误的设计!
ENGINE = ReplacingMergeTree(created_at)
ORDER BY (start_block, created_at, end_block, status, owner)

这里的问题在于:版本字段 created_at 出现在了 ORDER BY 子句中

为什么这样设计是错误的?

假设有以下数据:

 
 
start_block created_at end_block status owner
23379678 2025-09-30 14:55:47 23379687 init  
23379678 2025-09-30 14:55:46 23379687 init  
23379678 2025-09-30 14:55:45 23379687 init  

在 ClickHouse 看来,这些是完全不同的记录,因为:

  • (23379678, 2025-09-30 14:55:47, 23379687, init, )

  • (23379678, 2025-09-30 14:55:46, 23379687, init, )

  • (23379678, 2025-09-30 14:55:45, 23379687, init, )

由于 created_at 不同,每条记录的 ORDER BY 键都不同,因此永远不会触发去重机制。

正确的解决方案

1. 修正表结构

sql
-- 正确的设计
ENGINE = ReplacingMergeTree(created_at)
ORDER BY (start_block, end_block, status, owner)

现在,相同的 (start_block, end_block, status, owner) 组合被认为是重复数据,系统会保留其中 created_at 最大的版本。

2. Go 代码示例

go
err := db.Table(model.BlockTaskTableName).Set("gorm:table_options", "ENGINE = ReplacingMergeTree(created_at) "+"PARTITION BY intDiv(start_block, 500000) "+"PRIMARY KEY(start_block, end_block) "+"ORDER BY (start_block, end_block, status, owner) "+  // 关键:移除 created_at"SETTINGS enable_block_number_column = 1, enable_block_offset_column = 1").AutoMigrate(&model.BlockTask{})

3. 验证修正效果

修改后,相同的测试数据:

 
 
start_block created_at end_block status owner
23379678 2025-09-30 14:55:47 23379687 init  
23379678 2025-09-30 14:55:46 23379687 init  
23379678 2025-09-30 14:55:45 23379687 init  

现在 ClickHouse 认为这些是相同的记录(因为 ORDER BY 键相同),最终只保留 created_at = 2025-09-30 14:55:47 的那条记录。

重要注意事项

1. 后台合并的异步性

即使表结构正确,ReplacingMergeTree 的去重也是后台异步进行的:

sql
-- 手动触发合并(生产环境慎用)
OPTIMIZE TABLE block_tasks FINAL;

2. 实时精确查询的替代方案

如果业务要求100%实时精确,建议使用聚合查询替代 FINAL

sql
-- 方法1:使用子查询
SELECT * 
FROM block_tasks 
WHERE (start_block, end_block, created_at) IN (SELECT start_block, end_block, MAX(created_at)FROM block_tasks WHERE status = 'init'GROUP BY start_block, end_block
);-- 方法2:使用窗口函数
SELECT * FROM (SELECT *,ROW_NUMBER() OVER (PARTITION BY start_block, end_block ORDER BY created_at DESC) as rnFROM block_tasks WHERE status = 'init'
) WHERE rn = 1;

最佳实践总结

  1. 版本字段不入 ORDER BYReplacingMergeTree(version_field) 中的版本字段不应出现在 ORDER BY 子句中

  2. ORDER BY 定义去重粒度ORDER BY 字段的组合决定了什么算是"重复数据"

  3. 高基数字段在前:在 ORDER BY 中将高基数字段(如ID、时间戳)放在前面

  4. 接受最终一致性ReplacingMergeTree 适合能接受短暂数据延迟的场景

  5. 实时需求用聚合:需要实时精确去重时,使用聚合查询而非 FINAL

结论

ReplacingMergeTree 的去重失效通常不是因为 FINAL 关键字的问题,而是由于表结构设计不当。记住这个简单的原则:版本字段决定保留谁,ORDER BY 字段决定谁是重复。正确区分这两者的角色,就能避免这个常见的陷阱。

通过本文的解决方案,你的 FINAL 查询将能够正确工作,返回真正去重后的数据结果。

http://www.hskmm.com/?act=detail&tid=21543

相关文章:

  • js中?? 和 || 的区别详解
  • 微信机器人API接口| 个人开发者必备
  • 直击现场! “ 直通乌镇 ”开源赛复赛收官,OpenCSG担任评委,十强藏着哪些产业机会?
  • Python 列表生成式、字典生成式与生成器表达式
  • java 解析json字符串,获取特定的字段值,JsonObject
  • python 批量提取txt数据中的值写入csv
  • 回忆中学的函数
  • Java 一行一行的读取文本,小Demo 大学问
  • 家里wifi电信出口ip如何控制不变,解决访问云服务器上面的资源
  • 2025 年挤压造粒机源头厂家最新推荐榜单:前五企业技术实力、服务能力及口碑测评指南对辊挤压/化肥挤压/干粉挤压造粒机厂家推荐
  • MYSQL数据库取消表的约束
  • 2025 年京东 e 卡回收平台最新推荐排行榜:权威测评实时结算平台,助力用户安全高效转让京东 e 卡
  • 2025 年支付宝消费券回收平台最新推荐榜单:优质平台权威测评,助您高效安全处理闲置消费券支付宝消费券回收/闲置支付宝消费券回收/支付宝消费券快速回收平台推荐
  • ICP备案查询网站 域名备案查询
  • 2025 年注浆管厂家最新权威推荐排行榜:聚焦 R780/108 / 隧道 / 预埋 / 桩基等专用产品,精选 TOP5 优质企业
  • stable diffusion网络结构详解
  • 9.30
  • 网络与系统攻防技术实验一——逆向破解与Bof
  • 【python】解决grpcio.protoc生成的pb文件里面没有类和方法定义
  • 阙韩
  • “计算机配置\Windows 设置\安全设置\本地策略\审核策略” 配置后不生效
  • Spring Boot 事件发布与监听 观察者模式的实际应用 - 实践
  • P13969 [VKOSHP 2024] Exchange and Deletion
  • Matlab 通用库的fft和dsp toolbox的dsp.fft对比
  • [CTS2024] 众生之门
  • [CEOI 2025] Equal Mex
  • [ROI 2018] Quick sort
  • CF2127F Hamed and AghaBalaSar
  • 2025 年PPH 管厂家推荐榜单:江苏镇江扬中优质 PPH 管道/管材/管件厂家权威精选
  • Label-Free Liver Tumor Segmentation