用 Native 格式导出一份查询结果的原始字节,逐字节解码出 ClickHouse 处理器之间真正传递的最小单位——Block。
ClickHouse 处理器之间传递数据的最小单位不是"一行",而是"一个 Block"(一批列 + 一批行)。用 Native 格式可以看到这个 Block 在内存/网络里的原始字节。
导出3行2列的Block,用xxd看原始字节clickhouse-client --query "SELECT id, v FROM demo.olap_batch WHERE id<=3 ORDER BY id FORMAT Native" | xxd 00000000: 0203 0269 6406 5549 6e74 3634 0100 0000 ...id.UInt64.... 00000010: 0000 0000 0200 0000 0000 0000 0300 0000 ................ 00000020: 0000 0000 0176 0746 6c6f 6174 3634 0000 .....v.Float64.. 00000030: 0000 0000 f83f 0000 0000 0000 1440 0000 .....?.......@.. 00000040: 0000 0000 0c40 .....@
这一段字节就是一个的 Block,逐字节拆开看:
| 字节 | 含义 |
|---|---|
| 02 | 这个 Block 有 2 列 |
| 03 | 这个 Block 有 3 行 |
| 02 "id" 06 "UInt64" | 第1列:列名"id",类型"UInt64" |
| 01 00...00 / 02 00...00 / 03 00...00 | id列的3个值:1、2、3,连续排列 |
| 01 "v" 07 "Float64" | 第2列:列名"v",类型"Float64" |
| f8 3f... / 14 40... / 0c 40... | v列的3个值:1.5、5、3.5,连续排列 |
从上面这份字节流能直接验证:id列的3个值(1,2,3)先连续排完,然后才轮到v列的3个值(1.5,5,3.5),而不是"第1行(1,1.5)、第2行(2,5)、第3行(3,3.5)"这样按行交替排列。
v 列里的 5 和 3.5 不是巧合——这条查询就是本系列第1篇里跑过 ALTER TABLE UPDATE 之后的同一张表:id=2 的偶数行被翻倍成了5,id=1 和 id=3 保持原样。这里只是换了个角度,用最底层的字节确认这些值是怎么被组织和传输的。
Pipeline 里的每个处理器(Transform)的输入输出,单位都是 Block。
如果每次只传 1 行,处理器每处理一次就要重新解析一遍"这一行有哪些列、什么类型",这份表头开销会被重复 2000 万次;而传一个装了几万行的 Block,表头只需要解析 1 次,之后就是对一整段连续内存做批量运算——这跟本系列第4篇里测出的"max_block_size=1 时耗时暴涨361倍"是同一个原因的两个角度:那一篇看到的是墙钟时间的暴涨,这一篇看到的是这个暴涨背后,物理上到底在传递什么样的数据结构。
每一步都是本文上面导出的Native格式原始字节,按顺序拆解。
FORMAT Native:ClickHouse 内部处理器之间使用的二进制格式xxd:把原始字节流转成可读的十六进制