ClickHouse 中的 INSERT(写入)和 UPDATE(更新)是两种完全不同的操作,核心差异源于 ClickHouse 为 OLAP 场景设计的 "追加写优先、规避随机写" 的底层逻辑 ------INSERT 是原生优化的核心操作,而 UPDATE 是 "伪更新" 的兼容性功能,两者在执行逻辑、性能、适用场景上有本质区别。以下从 执行原理、性能特征、语法 / 行为、适用场景 四个维度详细对比:
一、核心执行原理差异
1. INSERT:原生追加写,高效批量执行
INSERT 是 ClickHouse 最核心的写入方式,完全适配列存 + MergeTree 引擎的设计,核心逻辑是追加写 + 异步批量刷盘:
- 执行流程 :
- 数据先写入内存缓冲区,同步写 WAL(预写日志)保证不丢;
- 缓冲区达到阈值后,异步刷盘生成小 Part(数据片段),按排序键有序存储;
- 后台 Merge 线程将小 Part 合并为大 Part,优化查询性能。
- 底层特征 :
- 纯顺序写(列存文件追加),无随机 IO 开销;
- 支持批量写入(最优 10 万~100 万行 / 次),单节点吞吐量可达百万行 / 秒;
- 写入后数据立即可见(内存缓冲区数据可查),刷盘 / 合并不影响可见性。
2. UPDATE:伪更新(Mutation 操作),标记删除 + 后台合并
ClickHouse 没有 OLTP 数据库的 "实时行级更新" 能力,UPDATE 本质是 Mutation(突变)操作,属于 "逻辑更新" 而非 "物理更新":
- 执行流程 :
- 执行
UPDATE时,不会直接修改目标数据的物理文件; - 先向系统提交一个 Mutation 任务(异步执行),记录 "需要更新的行条件 + 新值";
- 后台线程执行 Mutation 时,为目标行添加 "删除标记(Tombstone)",并写入新值作为新数据;
- 只有当包含标记的 Part 被后台 Merge 合并时,才会物理删除旧数据、保留新值。
- 执行
- 底层特征 :
- 本质是 "标记旧数据无效 + 写入新数据",涉及随机 IO(定位旧数据块);
- 操作粒度是 "数据片段(Part)",而非单行,需扫描整个 Part 匹配更新条件;
- 更新后的数据 "逻辑可见"(查询时过滤旧数据),但物理清理需等待 Merge。
二、性能特征对比
| 维度 | INSERT(写入) | UPDATE(更新) |
|---|---|---|
| 操作类型 | 原生优化的核心操作,批量追加写 | 兼容性功能,异步 Mutation 操作,伪更新 |
| IO 类型 | 顺序写(列存文件追加),IO 效率极高 | 随机 IO(定位旧数据块)+ 顺序写(新数据),IO 效率极低 |
| 吞吐量 | 批量写入可达百万行 / 秒(单节点) | 单行 / 小批量更新:每秒仅数百~数千行 |
| 并发能力 | 高并发批量写入友好(无锁冲突) | 低并发(Part 级锁,易冲突) |
| 阻塞风险 | 无(写入不阻塞查询,Merge 异步执行) | 有(Mutation 执行时会锁定 Part,查询需等待) |
| 资源消耗 | 低(仅内存缓冲 + 顺序写) | 高(需扫描 Part、标记数据、合并清理) |
三、语法与行为差异
1. 语法示例
(1)INSERT:支持批量写入,语法灵活
-- 1. 批量插入单行/多行
INSERT INTO t_device (device_id, ts, temperature)
VALUES ('dev001', '2025-01-01 10:00:00', 25.5),
('dev002', '2025-01-01 10:00:00', 26.1);
-- 2. 从查询结果插入(批量导入核心方式)
INSERT INTO t_device_stats
SELECT device_id, toHour(ts) AS stat_hour, avg(temperature)
FROM t_device
GROUP BY device_id, stat_hour;
-- 3. 从文件导入
INSERT INTO t_device FORMAT CSV INFILE '/data/device.csv';
(2)UPDATE:仅支持 WHERE 条件,异步执行
-- 更新满足条件的行(提交 Mutation 任务,立即返回,后台执行)
ALTER TABLE t_device UPDATE temperature = 26.0 WHERE device_id = 'dev001' AND ts = '2025-01-01 10:00:00';
-- 查看 Mutation 任务状态(确认是否执行完成)
SELECT * FROM system.mutations WHERE table = 't_device';
2. 关键行为差异
| 行为特征 | INSERT | UPDATE |
|---|---|---|
| 执行方式 | 同步接收数据(WAL 落盘),异步刷盘 / 合并 | 同步提交任务,异步执行(返回≠完成) |
| 数据可见性 | 写入后立即可见(内存缓冲区) | 需等待 Mutation 任务执行完成后才可见 |
| 原子性 | 批量写入原子性(要么全成功,要么全失败) | Part 级原子性,跨 Part 更新可能部分完成 |
| 事务支持 | 无 ACID,但 WAL 保证数据不丢 | 无 ACID,任务执行中中断可能导致数据不一致 |
| 撤销 / 回滚 | 不支持(追加写无法回滚) | 不支持(提交后无法取消) |
| 对 Merge 的影响 | 触发小 Part 生成,后台合并优化 | 生成删除标记,加速 Part 合并(额外开销) |
四、适用场景对比
1. INSERT:首选场景(ClickHouse 设计目标)
- 海量批量写入:如物联网设备数据上报、日志导入、用户行为数据批量同步(核心场景);
- 数据初始化 / 全量导入:如从 HDFS/MySQL 导入历史数据;
- "更新" 替代方案 :对需要更新的场景,优先通过
INSERT写入新数据,配合ReplacingMergeTree引擎按排序键自动去重(如按device_id+ts保留最新值)。
2. UPDATE:仅应急使用(规避高频操作)
- 少量数据修正:如偶发的错误数据修改(如设备上报的异常温度值);
- 低频次批量更新:如每天一次批量修正某类数据(避免每秒 / 每分钟执行);
- 禁止场景:高并发单行更新(如每秒更新单设备的最新状态)、频繁小批量更新(性能极差,易导致 Merge 阻塞)。
五、核心建议:优先用 INSERT 替代 UPDATE
ClickHouse 优化的核心是 "追加写",尽量避免直接使用 UPDATE,推荐以下替代方案:
-
ReplacingMergeTree 引擎 + INSERT 覆盖 :建表时指定
ReplacingMergeTree(版本字段)(如时间戳),更新数据时直接插入新值,后台 Merge 时自动保留最新版本:-- 建表:按 ts 保留最新版本
CREATE TABLE t_device (
device_id String,
ts DateTime,
temperature Float32
) ENGINE = ReplacingMergeTree(ts)
ORDER BY (device_id, ts);-- "更新":插入新值,Merge 后覆盖旧值
INSERT INTO t_device VALUES ('dev001', '2025-01-01 10:00:00', 26.0); -
分区级批量替换 :对按时间分区的表,通过
ALTER TABLE DROP PARTITION删除旧分区,再INSERT新数据(适合批量更新某时间段数据); -
物化视图预计算:对聚合数据的 "更新",通过物化视图实时聚合新数据,替代直接更新聚合结果。
总结
| 维度 | INSERT | UPDATE |
|---|---|---|
| 设计定位 | OLAP 核心写入操作,极致优化批量吞吐量 | OLTP 兼容操作,伪更新,性能差 |
| 底层逻辑 | 顺序追加写,内存缓冲 + 异步刷盘 | 标记删除 + 写入新数据,后台 Merge 物理清理 |
| 性能 | 批量写入:百万行 / 秒;单行写入:效率低 | 任何场景:效率极低,高并发下不可用 |
| 适用场景 | 批量数据导入、实时数据上报、覆盖式更新 | 少量数据修正、低频次批量更新 |
| 最佳实践 | 优先使用,配合 MergeTree 变体实现 "逻辑更新" | 尽量避免,仅应急使用 |
简言之:ClickHouse 的 INSERT 是 "为速度而生" 的批量写入操作,而 UPDATE 是 "为兼容而生" 的低效操作,生产中需围绕 INSERT 设计数据写入逻辑,而非依赖 UPDATE。