INSERT ... SELECT 查询。一项设置 deduplicate_insert 可控制同步和异步插入。INSERT ... SELECT 需要额外注意,并且有专属设置。请参阅控制插入去重的设置。
局限
插入状态不确定
去重窗口限制
*_deduplication_window 次其他插入操作,去重可能无法按预期生效。在这种情况下,相同的数据可能会被多次插入。
控制插入去重的设置
- 目标表保留去重日志。这是表级别的设置。
- 已为查询启用去重。这是查询级别的设置。
表级设置
*MergeTree 引擎支持插入时去重。
对于 *ReplicatedMergeTree 引擎,去重日志默认处于启用状态,由 replicated_deduplication_window 和 replicated_deduplication_window_seconds 设置控制。对于非复制表 *MergeTree 引擎,该日志由 non_replicated_deduplication_window 设置控制,其默认值为 0。因此,普通的 MergeTree 表在将该窗口设置为正值之前不会去重任何数据。
上述设置决定了表的去重日志参数。去重日志会存储有限数量的 block_id,而这些 block_id 会决定去重的实现方式 (见下文) 。
replicated_deduplication_window_for_async_inserts 和 replicated_deduplication_window_seconds_for_async_inserts 是旧版设置。同步和异步插入现在共用一个去重日志,因此 replicated_deduplication_window 同时控制两者。旧版设置仅限制旧的 ClickHouse Keeper 目录,这在滚动升级期间很重要。查询级别设置
deduplicate_insert 支持三个值:
enable— 对INSERT查询启用去重。disable— 对INSERT查询禁用去重。backward_compatible_choice— 由旧版设置insert_deduplicate(同步插入) 和async_insert_deduplicate(异步插入) 决定。
deduplicate_insert = disable 运行的查询不会为其块写入任何 block_id。即使之后使用 deduplicate_insert = enable 重试插入,这些数据也无法去重。目标表不保留去重日志时也是如此:不会记录任何内容,因此重试时也无法进行匹配。
优先次序
- 对于
INSERT ... SELECT查询,由deduplicate_insert_select决定。请参阅 INSERT … SELECT 去重。 - 对于所有其他
INSERT,由deduplicate_insert决定。 - 仅当
deduplicate_insert为backward_compatible_choice时,才会读取insert_deduplicate和async_insert_deduplicate。
旧版和已废弃设置
26.2 版本还将
async_insert 和 deduplicate_blocks_in_dependent_materialized_views 的默认值改为启用。compatibility 设置控制这三项设置。如果将 compatibility 设置为早于 26.2 的版本,这些设置将保留原有默认值:deduplicate_insert 会变为 backward_compatible_choice,并将决定权交给 insert_deduplicate 和 async_insert_deduplicate。显式指定的设置始终会生效,不受 compatibility 影响。
insert 去重的工作原理
*MergeTree 引擎的表,每个块都会分配一个唯一的 block_id,它是该块中数据的哈希值。这个 block_id 会作为插入操作的唯一键使用。如果在去重日志中发现相同的 block_id,则该块会被视为重复块,不会插入表中。
这种方法在 插入 包含不同数据的场景下效果很好。但是,如果你有意多次插入相同的数据,就需要使用 insert_deduplication_token 设置来控制去重过程。该设置允许你为每次 insert 指定一个唯一的标记,ClickHouse 会据此判断数据是否重复。insert_deduplication_token 具有更高的优先级:提供该标记时,ClickHouse 不会使用数据的哈希值。
对于 INSERT ... VALUES 查询,插入数据被拆分为块的方式是确定性的,并且由相关设置决定。因此,重试插入时应使用与初始操作相同的设置值。
INSERT ... SELECT 的去重
INSERT ... SELECT 查询,SELECT 部分必须在每次尝试时都以相同顺序返回相同的数据。否则,块会不同,block_id 也会不同,重试就不会被识别为重复操作。
ClickHouse 无法验证源数据是否未发生变化,但可以检查查询本身能否产生可复现的结果。同时满足以下两个条件时,SELECT 会被视为稳定:
- 查询包含
ORDER BY ALL子句。仅识别字面量ORDER BY ALL;普通的ORDER BY <expressions>不会被识别,并且由两个或更多SELECT组成的UNION永远不会稳定。 - 读取管道最终只有单个 stream。
insert_deduplication_token 可等效替代稳定性,因为此时标识插入操作的是标记,而非数据。
设置 deduplicate_insert_select 用于决定相应行为:
enable_when_possible 和 enable_even_for_bad_queries 也会遵循 deduplicate_insert:如果其值为 disable,则不会对查询进行去重。force_enable 会覆盖 deduplicate_insert。
请注意,所选表可能会在两次重试之间被更新。这两种方式的行为恰好相反:
- 不使用
insert_deduplication_token时,block_id根据数据计算。变更后的结果会生成不同的block_id,不会发生去重,重试会在首次尝试已写入的数据之上插入新数据。 - 使用
insert_deduplication_token时,仅由标记标识插入操作。重试会被识别为重复操作并被丢弃,即使它本会插入不同的数据。
异步插入的去重
async_insert,自 26.2 版本起默认启用) 在重试时的去重方式与同步插入相同。deduplicate_insert 同时控制两者,无需单独设置开关。
这两种插入类型还共用同一个去重日志,并以相同方式计算 block_id。因此,您可以将客户端从同步插入切换为异步插入,或反向切换,而不会影响去重;以一种模式发送的重试仍会被识别为另一种模式下发送尝试的重复项。对于依赖去重的表,将工作负载从同步插入迁移到异步插入同样是安全的。
在 26.2 版本之前,异步插入的去重默认处于禁用状态,由
async_insert_deduplicate 控制。现在,只有当 deduplicate_insert 为 backward_compatible_choice 时,才会读取该设置。异步插入的去重粒度
- 队列中的每个查询都会为该批次提供一个去重标记。
- 如果查询提供了
insert_deduplication_token,该标记即为其值;否则,该标记为此查询所提供行的哈希值。 - 批处理不会影响标记,
insert_deduplication_token也不会影响查询分组为批次的方式。
- 如果批次中的某个查询重复,ClickHouse 只会移除该查询的行,批次中的其余数据仍会正常插入。仅当一个 part 中的所有行都被移除时,才会完全跳过该 part。
- 如果同一批次中的两个查询携带相同的标记,第二个查询会在写入 part 前被丢弃。这一规则按分区生效:如果两个查询将行写入不同分区,则两者都会保留。
system.events 中的 DuplicatedAsyncInserts 和 SelfDuplicatedAsyncInserts 事件分别统计这两种情况。
异步插入与 materialized view
NOT_IMPLEMENTED 异常。
当视图的输出无法容纳在一个块中时,便会输出第二个块。max_block_size 用于设置一个块可容纳的行数。列转换、筛选和聚合不会增加行数,因此始终只会产生一个块。JOIN 可能会增加行数。结果不超过 max_block_size 时可以正常工作,超过该值则会失败。
若要通过输出多个块的视图进行插入,请将 deduplicate_blocks_in_dependent_materialized_views = 0,或使用同步插入。
使用 materialized view 的插入去重
replicated_deduplication_windowreplicated_deduplication_window_secondsnon_replicated_deduplication_window
deduplicate_blocks_in_dependent_materialized_views 控制,该设置自 26.2 版本起默认启用。两个开关都必须允许去重:deduplicate_insert 会对插入源表的数据进行去重,而 deduplicate_blocks_in_dependent_materialized_views 还会对依赖表中的数据进行去重。如果希望实现完整去重,请同时启用两者。
当向 materialized view 下游的表中插入块时,ClickHouse 会通过对一个字符串进行哈希来计算 block_id,该字符串由源表中的 block_id 和其他附加标识符组合而成。这可确保 materialized view 中的去重准确无误,使系统能够根据数据的原始插入情况对其进行区分,而不受数据到达 materialized view 下游目标表之前所执行转换的影响。
示例
materialized view 转换后生成的相同数据块
dst 表。select 产生了 2 个块——插入时生成 2 个 parts。这些 parts 包含的数据不同。
mv_dst 表中。这两个 parts 包含相同的数据,但并未被去重。
dst 和 mv_dst 表都生效。
插入时的相同数据块
select 会产生两个块——因此,插入到表 dst 中的也应该是两个块。然而,我们看到只有一个块被插入了表 dst。这是因为第二个块被去重了。它的数据相同,而且用于去重的键 block_id 也是一样的;block_id 是根据插入的数据计算出的哈希值。这种行为并不符合预期。这类情况虽然很少见,但理论上是可能发生的。为了正确处理这类情况,用户必须提供 insert_deduplication_token。下面的示例将说明如何修正这个问题:
使用 insert_deduplication_token 插入时的相同数据块
insert_deduplication_token 的优先级更高:提供 insert_deduplication_token 时,ClickHouse 不会使用数据的哈希值。
不同的插入操作经过转换后,会在 materialized view 的底层表中生成相同的数据
mv_dst 表中的数据却相同。由于源数据不同,因此不会发生去重。
不同 materialized view 向同一底层表插入等效数据
mv_dst (符合预期) 。
dst 和 mv_dst 两个表上都会被去重。