4. ReplacingMergeTree去重表设计与验证

ReplacingMergeTree去重表设计与验证 学习卡片

ReplacingMergeTree 的版本语义与去重时机

ReplacingMergeTree 在后台合并分区内的数据片段时,会按 ORDER BY 键(即排序键)识别重复行,并保留版本列值最大的那一行。如果建表时指定了 ver 列,则版本大的胜出;未指定版本列时,保留最后插入的那一行。需要特别注意的是:去重只发生在后台合并时,且只在单个分区内进行,查询时未合并的重复行仍然可见。

ReplacingMergeTree 去重机制:分区内合并 + 版本列裁决 插入数据(同一分区) user_id=101, ver=1, name=Alice user_id=101, ver=3, name=Alicia user_id=101, ver=2, name=Ally 后台合并 合并裁决规则 按 ORDER BY 键分组 user_id=101 → 3 行重复 比较 ver 列 ver=3 最大 → 保留 ver=1,2 被丢弃 合并后 合并后结果 user_id=101 ver=3, name=Alicia 仅保留 1 行 (版本最大者) 查询方式对比:合并前 vs 合并后 未合并时查询(无 FINAL) SELECT * FROM t WHERE user_id=101 返回 3 行(ver=1,2,3 全部可见) ⚠ 重复数据未消除 需等待后台合并或手动 OPTIMIZE 合并后查询 / FINAL 查询 SELECT * FROM t FINAL WHERE user_id=101 返回 1 行(ver=3, name=Alicia) ✓ 去重生效 FINAL 在查询时实时合并,代价较高

建表与验证操作

下面创建一个带版本列的 ReplacingMergeTree 表,插入同一主键的多版本数据,然后分别用普通查询、FINAL 查询和 OPTIMIZE 后的查询验证去重效果。

-- 1. 建表:以 user_id 为排序键,ver 为版本列
CREATE TABLE user_replacing
(
    user_id   UInt32,
    name      String,
    city      String,
    ver       UInt32
)
ENGINE = ReplacingMergeTree(ver)
ORDER BY user_id;

-- 2. 插入同一 user_id 的三条不同版本数据
INSERT INTO user_replacing VALUES
(101, 'Alice',  'Beijing',  1),
(101, 'Ally',   'Shanghai', 2),
(101, 'Alicia', 'Shenzhen', 3);

-- 3. 未合并时查询:三条都可见
SELECT * FROM user_replacing WHERE user_id = 101;
-- 结果:3 行(ver=1,2,3)

-- 4. FINAL 查询:查询时实时去重
SELECT * FROM user_replacing FINAL WHERE user_id = 101;
-- 结果:1 行(ver=3, name=Alicia, city=Shenzhen)

-- 5. 手动触发合并
OPTIMIZE TABLE user_replacing FINAL;

-- 6. 合并后普通查询:重复行已被物理清除
SELECT * FROM user_replacing WHERE user_id = 101;
-- 结果:1 行(ver=3, name=Alicia, city=Shenzhen)
验证要点:步骤 3 返回 3 行说明去重尚未发生;步骤 4 返回 1 行说明 FINAL 在查询时按版本列正确裁决;步骤 6 返回 1 行说明 OPTIMIZE 已触发后台合并并物理去重。三步结果对比即可确认 ReplacingMergeTree 的去重行为符合预期。
注意事项:ReplacingMergeTree 的去重只在单个分区内生效。如果建表时使用了分区键(如 PARTITION BY toYYYYMM(create_time)),跨分区的相同排序键不会被去重。此外,后台合并时机由 ClickHouse 自主调度,不保证实时性,对去重有强实时要求的场景应在查询时使用 FINAL。