真实在同一client进程里对比500条单行INSERT和1条批量INSERT的耗时,再验证part数失控时ClickHouse真实设置的两道保险丝。
代码(单行INSERT,同一client进程内顺序执行,排除进程启动开销)-- 500条语句写入同一个 .sql 文件,一次client进程顺序执行 INSERT INTO demo.sptest3 VALUES (0, 'row0'); INSERT INTO demo.sptest3 VALUES (1, 'row1'); ... (共500条) 代码(批量INSERT)INSERT INTO demo.sptest2 SELECT number, concat('row', toString(number)) FROM numbers(500);
500条单行INSERT语句(同一client进程顺序执行): → 真实耗时 32.293 秒(约64ms/条) → 结束后 system.parts 里真实还剩3个active part(后台merge已经在追) 500行数据,1条批量INSERT: → 真实耗时 0.056 秒 → 结束后 system.parts 里真实是1个part 真实差距:32.293 / 0.056 ≈ 577倍
这576倍不是网络延迟或者客户端启动的开销(本文特意把500条语句放进同一个client进程顺序执行,排除了每次重新起进程的成本),差距真实来自ClickHouse服务端每次INSERT要做的固定工作:创建一个新part目录、写.bin/.cmrk2/checksums.txt等一整套文件、fsync落盘、把新part原子地注册进表的元数据——这套流程不管这次INSERT写1行还是写100万行,固定开销都是真实存在的,写1行时这份固定开销就占了几乎全部耗时。
SELECT name, value FROM system.merge_tree_settings
WHERE name IN ('parts_to_delay_insert','parts_to_throw_insert','max_parts_in_total');
→ parts_to_delay_insert 1000 ← 真实超过这个数,INSERT开始被人为限速
parts_to_throw_insert 3000 ← 真实超过这个数,INSERT直接报错拒绝
max_parts_in_total 100000
真实触发场景(本文另一个实验里,同一个insert block分区数超限):
DB::Exception: Too many partitions for single INSERT block (more than 100).
Large number of partitions is a common misconception. It will lead to
severe negative performance impact, including slow server startup,
slow INSERT queries and slow SELECT queries. (TOO_MANY_PARTS)每一步都是本文上面用真实计时和真实报错跑出来的原始结果。
--queries-file 单进程顺序执行500条INSERTsystem.merge_tree_settings(parts_to_delay_insert / parts_to_throw_insert)