轻量级更新目前处于 Beta 阶段。
如果您遇到问题,请在 ClickHouse 仓库 中提交 issue。
UPDATE 语句会更新表 [db.]table 中与表达式 filter_expr 匹配的行。
之所以称其为“轻量级更新”,是为了与 ALTER TABLE ... UPDATE 查询区分开来;后者是一个重量级过程,会重写数据分区片段中的整列。
它仅适用于 MergeTree 表引擎家族。
filter_expr 必须是 UInt8 类型。此查询会将指定列在 filter_expr 取非零值的行中的值,更新为相应表达式的值。
这些值会使用 CAST 运算符转换为列类型。不支持更新用于计算主键或分区键的列。
示例
轻量级更新不会立即更新数据
UPDATE 通过 patch parts 实现——这是一种特殊的数据分区片段,只包含已更新的列和行。
轻量级 UPDATE 会创建补丁分区片段,但不会立即在存储中对原始数据进行物理修改。
更新过程类似于 INSERT ... SELECT ... 查询,但 UPDATE 查询会等到补丁分区片段创建完成后才返回。
更新后的值将会:
- 通过应用补丁在
SELECT查询中立即可见 - 仅会在后续的合并和变更过程中被物理物化
- 一旦所有活跃 parts 都已完成补丁物化,便会被自动清理
轻量级更新要求
MergeTree、ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree 引擎及其 Replicated 和 Shared 版本均支持轻量级更新。
要使用轻量级更新,必须通过表设置 enable_block_number_column 和 enable_block_offset_column 启用 _block_number 和 _block_offset 列的物化。
轻量级删除
DELETE 查询可以通过轻量级 UPDATE 的方式执行,而不是作为 ALTER UPDATE 变更执行。轻量级 DELETE 的实现由设置 lightweight_delete_mode 控制。
性能注意事项
- 更新延迟与
INSERT ... SELECT ...查询的延迟相当 - 仅写入已更新的列和值,而不是数据分区片段中的整列
- 无需等待当前正在运行的合并/变更完成,因此更新延迟可预测
- 轻量级更新支持并行执行
- 会给需要应用补丁的
SELECT查询带来额外开销 - 对于存在待应用补丁的数据分区片段中的列,将不会使用跳过索引。如果表中存在补丁分区片段,则不会使用投影,这也包括那些没有待应用补丁的数据分区片段。
- 过于频繁的小规模更新可能会导致“parts 过多”错误。建议将多次更新合并到单个查询中,例如将待更新的 id 放在
WHERE子句中的同一个IN子句里 - 轻量级更新适合更新少量行 (最多约占表的 10%) 。如果需要更新更多数据,建议使用
ALTER TABLE ... UPDATE变更
并发操作
update_sequential_consistency 和 update_parallel_mode 控制。
更新权限
UPDATE 需要 ALTER UPDATE 权限。要为指定用户授予在特定表上执行 UPDATE 语句的权限,请运行:
实现细节
_part- 原始 part 的名称_part_offset- 原始 part 中的行号_block_number- 原始 part 中该行的块编号_block_offset- 原始 part 中该行的块内偏移_data_version- 更新后数据的数据版本 (为UPDATE查询分配的块编号)
_part 和 _part_offset 排序。
补丁分区片段所属的分区与原始 part 不同。
补丁分区片段的分区 id 为 patch-<hash of column names in patch part>-<original_partition_id>。
因此,包含不同列的补丁分区片段会存储在不同的分区中。
例如,三个更新 SET x = 1 WHERE <cond>、SET y = 1 WHERE <cond> 和 SET x = 1, y = 1 WHERE <cond> 会在三个不同的分区中创建三个补丁分区片段。
补丁分区片段之间可以相互合并,以减少在 SELECT 查询中需要应用的补丁数量并降低开销。补丁分区片段的合并使用 ReplacingMergeTree 的合并算法,并以 _data_version 作为版本列。
因此,补丁分区片段始终会为 part 中每个更新过的行保存最新版本。
轻量级更新不会等待当前正在运行的合并和变更完成,而是始终基于数据分区片段的当前快照来执行更新并生成补丁分区片段。
因此,应用补丁分区片段时可能会出现两种情况。
例如,如果我们读取 part A,则需要应用补丁分区片段 X:
- 如果
X包含 partA本身。当执行UPDATE时,如果A没有参与合并,就会出现这种情况。 - 如果
X包含 partB和C,而它们被 partA覆盖。当执行UPDATE时,如果当时正在执行从 (B、C) ->A的合并,就会出现这种情况。
- 使用基于排序列
_part、_part_offset的合并。 - 使用基于
_block_number、_block_offset列的 join。
ALTER UPDATE- 高开销的UPDATE操作- 轻量级
DELETE- 轻量级DELETE操作 APPLY PATCHES- 强制将补丁物化到数据分区片段中 (变更操作)