ClickHouse两种数据更新机制——轻量级更新(Lightweight UPDATE) 和传统突变(ALTER TABLE UPDATE)——的核心信息、对比与演进进行了整合,并以商超会员系统为例,提供了从建表到查询的完整演示。
1. 📌 核心机制对比与演进
1.1 核心对比
| 特性 | 轻量级更新 (Lightweight UPDATE) | 传统突变 (Heavy Mutation) |
|---|---|---|
| 对应语句 | UPDATE ... SET ... WHERE |
ALTER TABLE ... UPDATE ... |
| 核心原理 | 创建补丁部件,查询时动态合并新旧数据。 | 重写整个数据部分。 |
| 延迟 | 低,与 INSERT...SELECT 相当,无需等待后台合并。 |
高,需等待后台重写完成,时长不可预测。 |
| I/O影响 | 小,仅写入被更新的列。 | 大,重写涉及的所有数据列。 |
| 数据可见性 | 立即可见,查询时自动应用补丁。 | 异步可见,默认仅在突变完成后可见(apply_mutations_on_fly=1可优化)。 |
| 适用场景 | 高频、小批量更新(建议<10%表数据),如会员积分、标签。 | 低频、大批量历史数据修正。 |
| 必需设置 |
建表时需设置
|
无特殊要求。 |
apply_mutations_on_fly参数 |
完全无效,无需关心。 | 设为 1 时,可在查询时“即时”应用变更,提升体验。 |
| 状态 | Beta功能,持续演进优化。 | 成熟稳定的原生功能。 |
1.2 必需要参数相详解
根据官方文档和技术实践,enable_block_offset_column = 1 是必需的,它与 enable_block_number_column = 1 必须同时启用,缺少任何一个都无法使用轻量级更新功能。
它们共同作用,为系统提供精确的行级定位能力,是实现“补丁部件”(Patch Part)机制的基础。具体分工如下:
| 参数 | 对应系统列 | 主要作用 | 为什么必需 |
|---|---|---|---|
enable_block_number_column = 1 |
_block_number |
记录数据行所属的块编号。 | 用于在数据块层面快速定位需要更新的行。 |
enable_block_offset_column = 1 |
_block_offset |
记录数据行在块内的偏移量。 | 用于在块内部精确定位到具体的行。缺少它会直接报错。 |
这两个参数共同生成的 _block_number 和 _block_offset 系统列,是轻量级更新在查询时能够准确、高效地将“补丁”与原始数据行合并的关键。
2. 功能演进脉络
轻量级更新功能是逐步发展的:其思想始于 v22.8 LTS 引入的轻量级DELETE,随后UPDATE语法作为Beta功能推出,并持续优化。在云原生环境中,其设计更好地与SharedMergeTree等引擎协同。
3. 🛒 商超会员场景全流程演示
下面我们创建两张表,分别演示两种更新方式。
1. 建表:为两种更新方式做准备
表1:支持轻量级更新的表
这张表可以同时接受UPDATE和ALTER UPDATE,但我们将主要用UPDATE。
CREATE TABLE member_info_light
(
member_id UInt64,
name String,
phone String,
total_points UInt32,
tags Array(String),
update_time DateTime DEFAULT now()
)
ENGINE = MergeTree
ORDER BY member_id
SETTINGS
enable_block_number_column = 1, -- 启用轻量级更新
enable_block_offset_column = 1;
表2:仅使用传统突变的表
这张表我们故意不启用轻量级更新设置,将只使用ALTER UPDATE。
CREATE TABLE member_info_heavy
(
member_id UInt64,
name String,
phone String,
total_points UInt32,
tags Array(String),
update_time DateTime DEFAULT now()
)
ENGINE = MergeTree
ORDER BY member_id;
-- 注意:没有 enable_block_number_column 设置
2. 插入测试数据
-- 向两张表插入相同的数据
INSERT INTO member_info_light VALUES
(1, '张三', '13800138001', 500, ['新会员'], now()),
(2, '李四', '13800138002', 1500, ['高价值', '喜爱生鲜'], now()),
(3, '王五', '13800138003', 800, [], now());
INSERT INTO member_info_heavy SELECT * FROM member_info_light;
3. 执行更新:对比两种方式
场景A:更新单个会员积分(小批量、高频场景)
-- 1. 在 light 表上使用轻量级UPDATE (推荐)
UPDATE member_info_light
SET total_points = total_points + 100,
update_time = now()
WHERE member_id = 1; -- 瞬间完成,数据立即可见
-- 2. 在 heavy 表上使用传统ALTER UPDATE (不推荐在此场景)
ALTER TABLE member_info_heavy
UPDATE total_points = total_points + 100, update_time = now()
WHERE member_id = 1; -- 进入异步队列,完成时间不确定
场景B:批量修正会员信息(中批量场景)
-- 1. 在 light 表上,如果影响行数预估在10%以内,仍可用UPDATE
UPDATE member_info_light
SET phone = CONCAT('+86-', phone),
update_time = now()
WHERE member_id IN (1, 3);
-- 2. 在 heavy 表上,或当更新行数很多时,使用ALTER UPDATE
ALTER TABLE member_info_heavy
UPDATE phone = CONCAT('+86-', phone), update_time = now()
WHERE member_id IN (1, 3);
4. 查询测试:验证更新效果与可见性
-- 查询 light 表,轻量级更新结果立即可见
SELECT * FROM member_info_light ORDER BY member_id;
-- 查询 heavy 表,传统突变可能还未完成
SELECT * FROM member_info_heavy ORDER BY member_id;
-- 可以通过此命令查看 heavy 表突变进度
SELECT mutation_id, command, is_done FROM system.mutations WHERE table = 'member_info_heavy';
3. 💡 关键实践建议与总结
-
如何选择更新方式?
-
像更新会员积分、状态、标签这类高频、小范围操作,且对延迟敏感,请使用轻量级更新 (
UPDATE)。 -
像批量历史数据清洗、大规模字段回填这类低频、大批量任务,应使用传统突变 (
ALTER TABLE UPDATE)。
-
-
使用轻量级更新的黄金法则:
-
控制范围:单次更新行数最好低于总行数的10%。
-
批量操作:避免循环逐条更新,用
WHERE ... IN一次处理一批。 -
监控部件:频繁小更新可能导致“部件过多”。可通过
SELECT count() FROM system.parts WHERE table = 'member_info_light' AND active;监控部件数量,必要时在业务低峰期执行OPTIMIZE TABLE member_info_light FINAL;来合并。
-
-
注意性能权衡:
轻量级更新以查询时微增的计算开销(需合并补丁)为代价,换取了更新时的极低延迟和I/O开销。如果业务是写多读少,优势明显;如果是读多写少,需关注补丁堆积对查询的影响。
总结:你可以将轻量级更新视为一把处理实时小规模数据变更的“精准手术刀”,而传统突变则是进行大规模数据重构的“重型器械”。在你的商超会员系统中,根据变更的“粒度”和“频率”正确选择工具,是保证系统高效稳定的关键。
如果你能提供具体的会员表数据规模和更新频率(例如,表总行数、每天大概更新多少次、每次更新影响多少行),我可以为你提供更具体的参数调优建议。
posted on
浙公网安备 33010602011771号