You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

ClickHouse如何确保主键对应唯一数据行?

解决ClickHouse中主键唯一性与重复数据的问题

首先得明确:ClickHouse 是面向OLAP的列式数据库,和传统OLTP数据库的主键约束逻辑差异很大——它没有原生的插入时强制唯一的约束,所有去重、合并逻辑都依赖MergeTree系列引擎的分区合并机制,这是为了极致的写入和查询性能做出的设计。下面针对你的两个场景分别给出解决方案:

场景1:确保ReplacingMergeTree表的主键行唯一(阻止重复插入或实时去重)

你当前使用的ReplacingMergeTree(uid),它的核心逻辑是在后台合并分区时,会保留相同排序键(这里是uid)的最后一行数据,但插入时不会做任何校验,所以查询时会看到重复行,直到分区合并触发。

方案1:插入前校验(阻止重复插入)

如果要完全阻止重复主键的插入,可以通过INSERT ... SELECT结合NOT EXISTS来实现,在插入前检查目标主键是否已存在:

-- 替换原INSERT语句,仅当uid不存在时插入
INSERT INTO test2 (uid, name)
SELECT '1', 'User4'
WHERE NOT EXISTS (SELECT 1 FROM test2 WHERE uid = '1');

这种方式能确保不会插入重复的uid,但缺点是每次插入都要做一次查询校验,会增加写入的开销,适合写入频率不高的场景。

方案2:实时查询去重(不阻止插入,但查询时返回唯一行)

如果可以接受插入重复数据,但查询时只返回最新的一行,可以在查询时加上FINAL关键字:

SELECT * FROM test2 FINAL WHERE uid = '1';

注意:FINAL会触发即时合并,会显著降低查询性能,不建议在高并发查询场景使用。如果对性能要求高,可以等待后台自动合并(通常几分钟到几十分钟,取决于配置),或者手动触发合并:

OPTIMIZE TABLE test2 FINAL;

场景2:统计表格的重复数据自动更新,保证求和正确

你的统计场景中,重复插入相同(date, blog_id)的数据导致求和错误,最合适的方案是使用ReplacingMergeTree或者AggregatingMergeTree来处理:

方案1:用ReplacingMergeTree替换重复行

修改statistics表的引擎为ReplacingMergeTree,指定date作为版本字段(或者新增一个自增version字段,确保新数据版本更高):

CREATE TABLE statistics (
 `date` UInt32,
 `blog_id` String,
 `read_cnt` UInt32,
 `like_cnt` UInt32
) ENGINE = ReplacingMergeTree(date) -- 用date作为版本,新的date(更大的值)会覆盖旧的
ORDER BY (date, blog_id); -- 排序键设为(date, blog_id),确保同组合的行被合并

当插入重复的(date, blog_id)数据时,后台合并后会保留最新版本(date更大的)的行。如果需要实时看到更新后的结果,查询时加FINAL

SELECT b.blog_writer as writer, SUM(a.read_cnt) as read_sum, SUM(a.like_cnt) as like_sum
FROM (
 SELECT blog_id, read_cnt, like_cnt FROM statistics FINAL
) a
JOIN blog b ON a.blog_id = b.blog_id
GROUP BY b.blog_writer;

方案2:用AggregatingMergeTree预先聚合(更适合统计场景)

如果你的场景主要是做聚合统计,推荐使用AggregatingMergeTree,它会在插入时自动聚合相同排序键的数据,从根源避免重复求和:

-- 创建带聚合引擎的表
CREATE TABLE statistics (
 `date` UInt32,
 `blog_id` String,
 `read_cnt` AggregateFunction(sum, UInt32),
 `like_cnt` AggregateFunction(sum, UInt32)
) ENGINE = AggregatingMergeTree()
ORDER BY (date, blog_id);

-- 插入数据时使用聚合函数
INSERT INTO statistics (date, blog_id, read_cnt, like_cnt)
SELECT 202008, '1', sumState(60), sumState(0);

-- 查询时使用merge函数获取聚合结果
SELECT b.blog_writer as writer, 
       sumMerge(a.read_cnt) as read_sum, 
       sumMerge(a.like_cnt) as like_sum
FROM (
 SELECT blog_id, read_cnt, like_cnt FROM statistics
) a
JOIN blog b ON a.blog_id = b.blog_id
GROUP BY b.blog_writer;

这种方式下,即使插入重复的(date, blog_id)数据,ClickHouse会自动将read_cnt和like_cnt累加(或者根据你定义的聚合函数处理),查询时直接得到正确的聚合结果,性能也更好。


内容的提问来源于stack exchange,提问作者Juneyoung Oh

火山引擎 最新活动