尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

ClickHouse ReplacingMergeTree 去重陷阱:为什么你的 FINAL 查询无效? - 若
📅 发布时间:2026/6/19 9:47:01

问题背景

在使用 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 BY:ReplacingMergeTree(version_field) 中的版本字段不应出现在 ORDER BY 子句中

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

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

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

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

结论

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

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

相关新闻

  • 微信机器人API接口| 个人开发者必备
  • MYSQL数据库取消表的约束
  • 2025 年京东 e 卡回收平台最新推荐排行榜:权威测评实时结算平台,助力用户安全高效转让京东 e 卡

最新新闻

  • 4.1 自由振动 固有频率与固有振型
  • ComfyUI TTP Toolset:3步掌握8K超分辨率图像分块处理技术,普通电脑也能轻松实现AI图像增强
  • LPC3130/3131 ARM9微控制器:多层AHB总线与引脚复用的嵌入式设计精要
  • 2026衡水2026正规漏水检测维修公司精选口碑榜TOP5权威推荐-精准定位检测漏水点-专业防水补漏堵漏维修、卫生间/厨房/屋顶/天沟/地下室/阳台防水漏水检测维修 - 安佳防水
  • 3种智能编排策略重构AI工作流创作效率
  • PPO算法在大语言模型RLHF训练中的工程实践与调参指南

日新闻

  • 信任的进化:技术实现详解——如何用JavaScript构建博弈论模拟器
  • Terrakube自定义工作流:如何集成OPA、Infracost等工具扩展IaC能力
  • grunt-concurrent快速入门:5分钟学会并行运行Grunt任务

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号