| 输入 | 输出 | 别名 |
|---|---|---|
| ✔ | ✔ |
描述
Native 格式是 ClickHouse 效率最高的格式,因为它是真正的“列式”格式,
也就是说,它不会将列转换为行。
在这种格式中,数据以二进制格式按块写入和读取。
对于每个块,会依次记录行数、列数、列名和类型,以及该块中各列的部分数据。
这是原生接口中用于服务器间交互、命令行客户端以及 C++ 客户端的格式。
数据类型传输格式
使用原生 TCP 二进制协议时 (或者当 HTTP 端点接收到
?client_protocol_version=<n> 时) ,
会在列数和行数之前写入一个 BlockInfo 结构。本节中的示例使用的是
不带协议版本的普通 HTTP 接口,因此会省略 BlockInfo。块结构
number 和 str,共 3 行:
多个块
简单数据类型
RowBinary/RowBinaryWithNamesAndTypes 类似。
符合这一描述的完整类型列表包括:
- (U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
- Float32, Float64
- Bool
- String
- FixedString(N)
- Date
- Date32
- DateTime
- DateTime64
- IPv4
- IPv6
- UUID
复杂数据类型
RowBinary 和 RowBinaryWithNamesAndTypes 不同。
- Nullable
- LowCardinality
- Array
- Map
- Variant
- Dynamic
- JSON
Nullable
Native 格式中,可空列在实际数据之前会有一段字节数据,其字节数等于块中的行数。每个字节都表示对应的值是否为 NULL。例如,在这个查询中,每个奇数都会改为 NULL:
Nullable(String) 的工作方式也类似。NULL 指示始终来自 nullable 掩码字节——
掩码值为 0x01 表示该行是 NULL,与字符串内容无关。对于 NULL 行,
底层字符串存储为空字符串 (LEB128 长度为 0) 。请注意,非 NULL 的空
字符串其 LEB128 长度也为 0,因此只有掩码字节才能区分这两种情况。例如,以下查询:
LowCardinality
LowCardinality 是透明的,Native format 使用基于字典的列式编码。一个列会被编码为版本前缀,随后是唯一值字典,以及一个指向该字典的整数索引数组。
列可以定义为
LowCardinality(Nullable(T)),但不能定义为 Nullable(LowCardinality(T)) —— 这始终会导致 server 返回错误。1 的 UInt64(LE),每列只写入一次。随后,每个块会写入以下内容:
UInt64(LE)—IndexesSerializationType位字段。位 0–7 表示索引宽度 (0 = UInt8,1 = UInt16,2 = UInt32,3 = UInt64) 。位 8 (NeedGlobalDictionaryBit) 在 Native format 中永远不会被设置 (如果遇到,server 会抛出异常) 。位 9 表示存在额外的 Dictionary key。位 10 表示应重置字典。UInt64(LE)— 字典键的数量,随后使用内部类型编码对这些键进行批量序列化。UInt64(LE)— 行数,随后使用相应的 UInt 宽度对索引值进行批量序列化。
String 的空字符串、数值类型的 0) 。对于 LowCardinality(Nullable(T)),索引 0 表示 NULL,并且这些键在序列化时不带 Nullable 包装。
例如,LowCardinality(String) 有 5 行 ['foo', 'bar', 'baz', 'foo', 'bar']:
LowCardinality(Nullable(String)),NULL 对应的索引为 0:
Array
- N 个累计的
UInt64偏移量 (小端序,每个 8 字节) 。第i行有offset[i] - offset[i-1]个元素,其中offset[-1]默认视为 0。 - 所有行中的全部嵌套元素会被连续地批量序列化。
Array(UInt32) 有 3 行 [[0, 10], [1, 11], [2, 12]]:
[[], ['0'], ['0','1'], ['0','1','2']] 的 Array(String):
Map
Map(K, V) 编码为 Array(Tuple(K, V))——先是数组偏移量,接着是所有键,最后是所有值。这与 RowBinary 不同,后者会按每个条目交错存储键和值。
例如,Map(String, UInt64) 有 3 行:[{'a':0,'b':10}, {'a':1,'b':11}, {'a':2,'b':12}]
Variant
Variant 列的编码方式如下:
UInt64(LE)判别值模式前缀 (0= BASIC,1= COMPACT) 。Native format 输出通常使用 BASIC (0) ;读取启用了use_compact_variant_discriminators_serialization的已存储数据时,可能会出现 COMPACT 模式。- N 个
UInt8判别值,每行一个。 - 每个 Variant 类型的数据都会编码为单独的批量列,其中仅包含与其匹配的行,并按判别值顺序排列。
Variant(String, UInt32) 有 5 行 [0::UInt32, 'hello', NULL, 3::UInt32, 'hello'] (排序后:String = 0,UInt32 = 1) :
Dynamic
Dynamic 序列化为结构前缀,后接一个 Variant 列。
结构前缀包含一个 UInt64(LE) 序列化版本,随后是动态类型的数量 (以 VarUInt 表示) ,再后面是以字符串形式表示的类型名称。在 V1 版本中,出于兼容性考虑,类型数量会写入两次。后续数据是一个 Variant 列,其类型列表由这些动态类型以及一个内部 SharedVariant 类型组成,并按字母顺序排序。
例如,包含 5 行 [0::UInt32, 'hello', NULL, 3::UInt32, 'hello'] 的 Dynamic:
JSON
JSON。这种编码较为复杂,并且依赖于版本:它由一个结构前缀组成,其中包含序列化版本、动态路径名称和共享数据布局;随后依次是类型化路径 (每个都作为一整列) 、动态路径 (每个都作为一个 Dynamic 列) ,以及用于溢出路径的共享数据。
为了获得更简单的互操作性,可以考虑使用设置 output_format_native_write_json_as_string=1,它会将 JSON 列序列化为普通的 JSON 文本字符串 (每行一个 String) 。