news 2026/4/20 19:53:00

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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运算符在查询时实现去重以及删除记录的剔除,最终获得准确的结果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 18:06:13

如何让Windows轻松接收AirPlay投屏?3个秘诀打破生态壁垒

如何让Windows轻松接收AirPlay投屏?3个秘诀打破生态壁垒 【免费下载链接】airplay2-win Airplay2 for windows 项目地址: https://gitcode.com/gh_mirrors/ai/airplay2-win 还在为iOS设备无法直接投屏到Windows电脑而烦恼吗?现在通过开源项目Airp…

作者头像 李华
网站建设 2026/4/18 15:17:31

革新性多平台账户保护:WinAuth本地加密验证解决方案

革新性多平台账户保护:WinAuth本地加密验证解决方案 【免费下载链接】winauth Authenticator on Windows for Battle.net / Steam / Guild Wars 2 / Glyph / Runescape / SWTOR / Bitcoin and digital currency exchanges 项目地址: https://gitcode.com/gh_mirro…

作者头像 李华
网站建设 2026/4/18 5:14:13

如何用MarkText重新定义你的写作体验?

如何用MarkText重新定义你的写作体验? 【免费下载链接】marktext 📝A simple and elegant markdown editor, available for Linux, macOS and Windows. 项目地址: https://gitcode.com/gh_mirrors/ma/marktext 在信息爆炸的时代,选择一…

作者头像 李华
网站建设 2026/4/18 20:36:17

Qwen3-TTS-Tokenizer-12HzGPU利用率:监控指标解读与瓶颈定位实战方法

Qwen3-TTS-Tokenizer-12Hz GPU利用率:监控指标解读与瓶颈定位实战方法 1. 为什么GPU利用率成了关键线索? 你有没有遇到过这种情况:模型明明跑起来了,Web界面显示“🟢 模型就绪”,但上传一段30秒的音频&am…

作者头像 李华
网站建设 2026/4/18 7:35:53

AMD显卡CUDA兼容与性能优化完全配置指南

AMD显卡CUDA兼容与性能优化完全配置指南 【免费下载链接】ZLUDA CUDA on AMD GPUs 项目地址: https://gitcode.com/gh_mirrors/zlu/ZLUDA 探索GPU计算的边界:当AMD遇见CUDA 想象一下,你手握着最新的AMD Radeon显卡,却面对众多仅支持N…

作者头像 李华
网站建设 2026/4/18 3:32:29

KiCad + STM32电源管理电路设计:完整示例解析

以下是对您提供的博文内容进行 深度润色与专业重构后的版本 。我以一位深耕嵌入式硬件设计十年、长期使用 KiCad 进行量产项目开发的工程师视角,重写了全文—— 去模板化、去AI腔、强逻辑、重实战、有温度、带思考痕迹 。全文严格遵循您的所有格式与风格要求&am…

作者头像 李华