在2000万行表上,把 max_block_size 从1一路调到默认的65505,看同一条SUM聚合的墙钟耗时怎么从32.8秒掉到0.09秒。
插入 2000 万行数据,跑同一条 SUM 聚合,唯一变量是每次处理多少行为一批。
插入INSERT INTO demo.big SELECT number, sin(number) FROM numbers(20000000) SELECT count() FROM demo.big → 20000000 同一条查询,只改 max_block_sizeSELECT sum(v) FROM demo.big SETTINGS max_block_size = N
ClickHouse 执行查询时不是一行一行地处理,而是把数据切成一批一批的"块"(Block),每批固定装 max_block_size 行,函数和聚合器每次调用处理一整批。这条设置直接决定了"一批"有多大。
同一条 SUM 查询,测出的墙钟耗时。
计时(同一台机器,同一份2000万行数据)max_block_size=1 → 32.836 s max_block_size=100 → 0.446 s max_block_size=1000 → 0.121 s max_block_size=8192 → 0.097 s max_block_size=65505 → 0.091 s(ClickHouse 默认值)
从 1 行一批加到 100 行一批,耗时就从 32.8 秒降到 0.45 秒,快了 73 倍;再加到默认的 65505 行一批,总共快了 约 361 倍。而从 8192 加到 65505 几乎没有区别(0.097s → 0.091s)——说明批量大到一定程度之后,"每批处理的开销均摊"这件事的收益就趋于饱和了。
2000 万行、每行都要单独走一次聚合器和表达式求值的完整流程,开销出在哪。
把 max_block_size 设成 1,聚合器 AggregatingTransform、表达式计算 ExpressionTransform 每次只处理 1 行,但每次调用仍然要走完整的一套:检查数据类型、可能触发一次虚函数调用、更新内部状态……这些"每次调用固定要做的事"跟本次真正要做的计算(一次浮点数加法)比起来,开销反而占了大头。批量处理把"调用次数"从 2000 万次压到了 20000000/65505 ≈ 305 次,固定开销总量直接压缩了 65505 倍,剩下的浮点加法本身在现代 CPU 上几乎不算什么。
每一步都是本文上面在2000万行表上跑出来的原始耗时。
numbers() 表函数生成)SETTINGS max_block_size=N:控制每批处理多少行