跳转到主要内容
轻量级更新目前处于 Beta 阶段。 如果您遇到问题,请在 ClickHouse 仓库 中提交 issue。
轻量级 UPDATE 语句会更新表 [db.]table 中与表达式 filter_expr 匹配的行。 之所以称其为“轻量级更新”,是为了与 ALTER TABLE ... UPDATE 查询区分开来;后者是一个重量级过程,会重写数据分区片段中的整列。 它仅适用于 MergeTree 表引擎家族。
UPDATE [db.]table [ON CLUSTER cluster] SET column1 = expr1 [, ...] [IN PARTITION partition_expr] WHERE filter_expr;
filter_expr 必须是 UInt8 类型。此查询会将指定列在 filter_expr 取非零值的行中的值,更新为相应表达式的值。 这些值会使用 CAST 运算符转换为列类型。不支持更新用于计算主键或分区键的列。

示例

UPDATE hits SET Title = 'Updated Title' WHERE EventDate = today();

UPDATE wikistat SET hits = hits + 1, time = now() WHERE path = 'ClickHouse';

轻量级更新不会立即更新数据

轻量级 UPDATE 通过 patch parts 实现——这是一种特殊的数据分区片段,只包含已更新的列和行。 轻量级 UPDATE 会创建补丁分区片段,但不会立即在存储中对原始数据进行物理修改。 更新过程类似于 INSERT ... SELECT ... 查询,但 UPDATE 查询会等到补丁分区片段创建完成后才返回。 更新后的值将会:
  • 通过应用补丁在 SELECT 查询中立即可见
  • 仅会在后续的合并和变更过程中被物理物化
  • 一旦所有活跃 parts 都已完成补丁物化,便会被自动清理

轻量级更新要求

MergeTreeReplacingMergeTreeCollapsingMergeTreeVersionedCollapsingMergeTree 引擎及其 ReplicatedShared 版本均支持轻量级更新。 要使用轻量级更新,必须通过表设置 enable_block_number_columnenable_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_consistencyupdate_parallel_mode 控制。

更新权限

UPDATE 需要 ALTER UPDATE 权限。要为指定用户授予在特定表上执行 UPDATE 语句的权限,请运行:
GRANT ALTER UPDATE ON db.table TO username;

实现细节

补丁分区片段与常规 parts 相同,但只包含更新后的列以及若干系统列:
  • _part - 原始 part 的名称
  • _part_offset - 原始 part 中的行号
  • _block_number - 原始 part 中该行的块编号
  • _block_offset - 原始 part 中该行的块内偏移
  • _data_version - 更新后数据的数据版本 (为 UPDATE 查询分配的块编号)
平均下来,在补丁分区片段中,每个更新过的行大约会增加 40 字节的额外开销 (未压缩数据) 。 系统列有助于定位原始 part 中需要更新的行。 系统列与原始 part 中的虚拟列相关;如果需要应用补丁分区片段,这些列会在读取时被添加。 补丁分区片段按 _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 包含 part A 本身。当执行 UPDATE 时,如果 A 没有参与合并,就会出现这种情况。
  • 如果 X 包含 part BC,而它们被 part A 覆盖。当执行 UPDATE 时,如果当时正在执行从 (BC) -> A 的合并,就会出现这种情况。
针对这两种情况,分别有两种应用补丁分区片段的方式:
  • 使用基于排序列 _part_part_offset 的合并。
  • 使用基于 _block_number_block_offset 列的 join。
join 模式比合并 模式更慢,且需要更多内存,但使用频率较低。
最后修改于 2026年6月10日