起一个本地 ClickHouse 26.9 单节点,用同一份 200 行数据分别做 200 次单行 INSERT 和 1 次批量 INSERT,再用 ALTER TABLE UPDATE 验证“更新”在 ClickHouse 里到底是什么。
同一张 MergeTree 表,写入完全相同的 200 行数据,只是写入方式不同。
本机 clickhouse-client,计时for i in $(seq 1 200); do clickhouse-client --query "INSERT INTO demo.olap_single (id, v) VALUES ($i, $i.5)" done # 耗时: 23.641 s clickhouse-client --query "INSERT INTO demo.olap_batch (id, v) VALUES (1,1.5),(2,2.5),...,(200,200.5)" # 耗时: 0.139 s
数据完全一样,写法不同,耗时差了 170 倍。这不是网络延迟的问题——两次测试都在同一台机器、同一个本地 TCP 连接上跑。真正的原因在 ClickHouse 怎么落盘。
直接查 system.parts,看两张表现在的 part 状态。
SELECT name, level, rows FROM system.parts WHERE table='olap_single' AND activeall_1_194_38 38 194 all_195_199_1 1 5 all_200_200_0 0 1 SELECT name, level, rows FROM system.parts WHERE table='olap_batch' AND activeall_1_1_0 0 200
olap_batch 只有 1 个 part,level=0,说明它从没被合并过——一次 INSERT 直接落盘成 1 个 part,之后再没有其他 part 需要跟它合并。
olap_single 这边,虽然建了 200 个 part,但活跃 part 只剩 3 个:其中 all_1_194_38 这个 part 的 level=38,意味着它是经过 38 轮后台合并才把最初 194 个零散 part 拼成 1 个的。level 每合并一轮 +1,38 这个数字就是后台执行了多少轮 merge 的直接证据。
对 olap_batch 执行一次 ALTER TABLE ... UPDATE,看它到底做了什么。
执行并查询 system.mutationsALTER TABLE demo.olap_batch UPDATE v = v * 2 WHERE id % 2 = 0 SELECT table, mutation_id, is_done FROM system.mutations → olap_batch mutation_2.txt 1 SELECT id, v FROM demo.olap_batch WHERE id IN (1,2,3,4) ORDER BY id → 1 1.5 (奇数,未命中 WHERE,原样) → 2 5 (偶数,2.5*2,生效) → 3 3.5 (奇数,原样) → 4 9 (偶数,4.5*2,生效)
UPDATE 语句生效了,偶数 id 的 v 值确实翻了倍。但它的执行方式跟 MySQL 那种"原地改一行"完全不同:ClickHouse 把这次 UPDATE 记录成一条 mutation(mutation_id=mutation_2.txt),后台异步地把受影响的 part 整个重写一遍,不是真的去改磁盘上的某几个字节。数据量小的时候这个过程快到几乎瞬间完成(is_done 立刻变成 1),但如果 part 很大,一次 UPDATE 要重写整个 part 文件。
每一步都是本文上面起 ClickHouse 单节点跑出来的原始结果。
system.parts:查看 part 的 name / level / rows / active 状态system.mutations:查看 ALTER UPDATE 产生的 mutation 是否完成