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

ClickHouse 实战:深入了解 MergeTree 家族 II 之 ReplacingMergeTree 表引擎

1. 概述

特点:一定程度上解决了重复数据的问题,适用于在后台清理重复数据以节省存储空间

虽然 MergeTree 拥有主键,但是它的主键却没有唯一键的约束。这意味着即便多行数据的主键相同,它们还是能够被正常写入。在某些使用场合,用户并不希望数据表中含有重复的数据。ReplacingMergeTree就是在这种背景下为了数据去重而设计的,它能够在合并数据分片 Part 时删除重复的数据,在保证查询性能的同时,实现了"最终一致性"的数据更新模型。它的出现,确实也在一定程度上解决了重复数据的问题。

为什么说是“一定程度”​?下面会详细介绍

ReplacingMergeTree表引擎继承自MergeTree基础表引擎,并对数据分片 Part 的合并逻辑进行了调整。ReplacingMergeTree会将所有具有相同排序键的行在数据分片 Part 合并时合并为一行,只保留指定版本的最新行。ReplacingMergeTree通过表的ORDER BY子句,而非PRIMARY KEY来删除重复记录。即行的唯一性是由表的ORDER BY子句决定的,而不是由PRIMARY KEY决定。与常规数据库的 UPDATE 操作不同,ReplacingMergeTree的更新是"异步"和"延迟"的,只在数据合并时发生。合并发生在后台未知时间,因此无法提前规划,且部分数据可能长时间保持未处理状态(重复数据没有被删除)。

尽管可以通过 OPTIMIZE 查询触发一次临时合并,但不要依赖这种方式,因为 OPTIMIZE 查询会读写大量数据。

因此,ReplacingMergeTree适用于在后台清理重复数据以节省存储空间,但并不能保证数据中完全不存在重复项。

2. 语法

CREATETABLE[IFNOTEXISTS][db.]table_name[ONCLUSTER cluster](name1[type1][DEFAULT|MATERIALIZED|ALIAS expr1],name2[type2][DEFAULT|MATERIALIZED|ALIAS expr2],...)ENGINE=ReplacingMergeTree([ver[,is_deleted]])[PARTITIONBYexpr][ORDERBYexpr][PRIMARYKEYexpr][SAMPLEBYexpr][SETTINGS name=value,...]

从上面可以看到创建一张ReplacingMergeTree表的方法与创建普通MergeTree表无异,只需要替换 Engine:

ENGINE=ReplacingMergeTree([ver[,is_deleted]])

其中,ver是一个表示版本号的可选参数。is_deleted是一个表示当前行状态的可选参数,只有在使用ver时才可以启用is_deleted

3. 特性

3.1 版本控制策略

可以通过指定一个UInt*Date或者DateTime类型的字段作为版本号ver来决定数据如何去重。在合并时,ReplacingMergeTree会在所有具有相同排序键的行中只保留一行:

  • 显式版本控制:如果指定了版本号ver,则保留具有最大版本号的行。如果多行的ver相同,保留最新插入的那一行。
  • 隐式版本控制:如果未设置版本号ver,则保留最新插入的那一行。
3.1.1 显式版本控制

显式版本控制是指指定版本号ver,那么就会保留具有最大版本号的行。如果多行的版本号相同,保留最新插入的那一行。版本列最常用的方式是时间戳或递增ID:

CREATETABLEreplacing_merge_tree_v1(id String,code String,create_timeDateTime)ENGINE=ReplacingMergeTree(create_time)PARTITIONBYtoYYYYMM(create_time)ORDERBYid;

replacing_merge_tree_v1基于 id 字段(排序键)去重,并且使用 create_time 字段作为版本号。现在向表中插入如下数据:

INSERTINTOreplacing_merge_tree_v1Values(1,'A3','2026-01-01 01:01:01');INSERTINTOreplacing_merge_tree_v1Values(1,'A2','2026-01-01 01:01:01');INSERTINTOreplacing_merge_tree_v1Values(1,'A1','2026-01-01 00:00:00');

那么在删除重复数据的时候,会在 id 相同时保留 create_time 最大最新的那一行:

SELECT*FROMreplacing_merge_tree_v1 FINAL;┌─id─┬─code─┬─────────create_time─┐ │1│ A2 │2026-01-0101:01:01│ └────┴──────┴─────────────────────┘

FINAL 语法下面会详细介绍。

3.1.2 隐式版本控制(无版本列)

隐式版本控制是指不指定版本列,ReplacingMergeTree默认保留最后插入的行:

CREATETABLEreplacing_merge_tree_v2(id String,code String,create_timeDateTime)ENGINE=ReplacingMergeTree()PARTITIONBYtoYYYYMM(create_time)ORDERBYid;

replacing_merge_tree_v2还是基于 id 字段(排序键)去重,但相比于replacing_merge_tree_v1没有指定版本号ver。现在向表中插入如下数据:

INSERTINTOreplacing_merge_tree_v2Values(1,'A3','2026-01-01 01:01:01');INSERTINTOreplacing_merge_tree_v2Values(1,'A2','2026-01-01 01:01:01');INSERTINTOreplacing_merge_tree_v2Values(1,'A1','2026-01-01 00:00:00');

那么在删除重复数据的时候,会在 id 相同时保留最后插入的行:

SELECT*FROMreplacing_merge_tree_v2 FINAL;┌─id─┬─code─┬─────────create_time─┐ │1│ A1 │2026-01-0100:00:00│ └────┴──────┴─────────────────────┘

3.2 状态控制

版本号ver决定了相同排序键(ORDER BY)行的保留优先级,而is_deleted则标记行的逻辑状态(UInt8类型的可选参数):1 表示行被删除,0 表示行未被删除(有效行):

CREATETABLEreplacing_merge_tree_v3(id String,code String,create_timeDateTime,is_deleted UInt8)ENGINE=ReplacingMergeTree(create_time,is_deleted)PARTITIONBYtoYYYYMM(create_time)ORDERBYid;

需要注意的是只有在使用ver时才可以启用is_deleted

replacing_merge_tree_v3还是基于 id 字段(排序键)去重,同时指定版本号ver,但相比于replacing_merge_tree_v1还设置行状态标记is_deleted。现在向表中插入如下数据:

INSERTINTOreplacing_merge_tree_v3Values(1,'A1','2026-01-01 01:01:01',0);INSERTINTOreplacing_merge_tree_v3Values(1,'A1','2026-01-01 01:01:01',1);

那么在合并删除重复数据时,根据版本号会保留版本号最大的一行数据,如果插入的两行数据具有相同的版本号,则会保留最后插入的那一行。由于最后一行is_deleted= 1 表示被删除(删除行),所以查询无返回行:

SELECT*FROMreplacing_merge_tree_v3 FINAL;0rowsinset.Elapsed:0.003sec.

3.3 查询模式 & FINAL

在合并阶段,ReplacingMergeTree使用 ORDER BY 列中的值作为唯一标识来识别重复行,并仅保留版本最高的那一行。不过,这种方式只能在最终状态上接近正确,它并不保证查询时所有重复行都会被去重,因此不应将其作为严格依赖。由于更新和删除记录在查询时仍可能被计算在内,查询结果因此可能不正确。为了获得准确的结果,用户需要在后台合并的基础上,再配合查询时去重以及删除记录的剔除来实现,这就需要通过FINAL运算符来完成这一需求。

假设我们有如下表:

CREATETABLEreplacing_merge_tree_v4(id String,code String,create_timeDateTime)ENGINE=ReplacingMergeTree(create_time)PARTITIONBYtoYYYYMM(create_time)ORDERBYid;

replacing_merge_tree_v4还是基于 id 字段(排序键)去重,同时指定版本号ver。现在向表中插入如下数据:

INSERTINTOreplacing_merge_tree_v4Values(1,'A3','2026-01-01 01:01:01');INSERTINTOreplacing_merge_tree_v4Values(1,'A2','2026-01-01 01:01:01');INSERTINTOreplacing_merge_tree_v4Values(1,'A1','2026-01-01 00:00:00');

在不使用FINAL的情况下进行查询返回结果没有达到去重的效果(具体情况会因合并情况而异):

SELECT*FROMreplacing_merge_tree_v4;┌─id─┬─code─┬─────────create_time─┐ │1│ A2 │2026-01-0101:01:01│ └────┴──────┴─────────────────────┘ ┌─id─┬─code─┬─────────create_time─┐ │1│ A1 │2026-01-0100:00:00│ └────┴──────┴─────────────────────┘ ┌─id─┬─code─┬─────────create_time─┐ │1│ A3 │2026-01-0101:01:01│ └────┴──────┴─────────────────────┘

添加FINAL后得到了预期结果:

SELECT*FROMreplacing_merge_tree_v4 FINAL;┌─id─┬─code─┬─────────create_time─┐ │1│ A2 │2026-01-0101:01:01│ └────┴──────┴─────────────────────┘

因此可以通过FINAL运算符在查询时实现去重以及删除记录的剔除,最终获得准确的结果。

http://www.jsqmd.com/news/310945/

相关文章:

  • TVBoxOSC容器化部署:3步实现跨平台部署,告别环境配置烦恼
  • HY-Motion 1.0参数调优:temperature/top_k/seed对动作多样性影响
  • 动手实操MGeo模型,真实地址数据测试结果分享
  • WinAuth:解决多平台账户安全验证难题的本地加密方案 | 多账户管理者必备
  • AI 净界行业落地:AI 生成贴纸制作中的图像分割应用
  • Notepad--跨平台高效编辑入门指南
  • selenium 自动化测试工具实战项目(客户)
  • ChatGLM-6B多轮对话能力:支持文件上传(txt/pdf)内容问答扩展
  • 工业通信接口PCB设计(RS485/CAN):操作指南
  • SiameseUniNLU基础教程:Pointer Network解码器如何精准定位中文Span边界(含位置编码分析)
  • 游戏化编程教育:突破教学困境的创新路径
  • 快速入门指南:fft npainting lama图像编辑区功能详解
  • 3分钟掌握消息留存工具:高效解决方案与零门槛实施指南
  • Z-Image-Turbo实战案例:用轻量镜像实现毫秒级文生图生产落地
  • 3大突破:CodeCombat如何让编程教育参与度提升150%
  • 小白也能用!Speech Seaco Paraformer ASR语音转文字保姆级教程
  • Unity战争迷雾系统开发指南:从基础实现到高级优化
  • Z-Image-ComfyUI跨境电商应用:多语言商品图生成实战
  • 颠覆跨生态投屏体验:零成本打造Windows AirPlay接收器,告别设备壁垒
  • SiameseUIE Web界面高级技巧:多Schema切换、历史记录回溯、结果差异高亮对比
  • MedGemma-X Gradio扩展协议:支持HL7/FHIR标准消息交互的中间件开发
  • SenseVoice Small效果对比:不同信噪比下中英文识别准确率曲线
  • Hunyuan-MT-7B-WEBUI启动教程:Jupyter操作不复杂
  • 解锁AI工具增强新可能:全面提升开发效率的完整方案
  • 3大颠覆性工具让科研人员彻底解决文献管理混乱难题
  • SiameseUIE中文-base部署案例:Docker镜像封装与生产环境端口映射
  • 多尺度特征融合实战:模型如何兼顾细节与整体语义?
  • 青龙面板任务自动化指南:从入门到精通的7个实用技巧
  • 简单三步走:用GPEN镜像实现高质量人像增强
  • MultiHighlight:代码阅读的效率革命工具