跳转到主要内容

描述

Native 格式的完整官方规范可在这里查看,而 Native 协议的配套规范——即承载该格式的 TCP 线协议——可在这里查看。
这两份规范都是由 LLM 根据 ClickHouse 源代码生成的。代码仍然是主要的事实依据:如果规范与代码不一致,以代码为准。
Native 格式是 ClickHouse 效率最高的格式,因为它是真正的“列式”格式, 也就是说,它不会将列转换为行。 在这种格式中,数据以二进制格式按写入和读取。 对于每个块,会依次记录行数、列数、列名和类型,以及该块中各列的部分数据。 这是原生接口中用于服务器间交互、命令行客户端以及 C++ 客户端的格式。
你可以使用这种格式快速生成只能由 ClickHouse DBMS 读取的转储。 但这种格式通常不适合手动处理。

数据类型传输格式

数据在线上传输时采用列式格式,这意味着每一列都会单独发送, 并且一列中的所有值会作为一个数组一起发送。 块中的每一列都包含一个头部,类似于 RowBinaryWithNamesAndTypes
使用原生 TCP 二进制协议时 (或者当 HTTP 端点接收到 ?client_protocol_version=<n> 时) , 会在列数和行数之前写入一个 BlockInfo 结构。本节中的示例使用的是 不带协议版本的普通 HTTP 接口,因此会省略 BlockInfo

块结构

以下查询返回两列:numberstr,共 3 行:
输出数据可放入单个 ClickHouse 块中,形式如下:

多个块

不过,在很多情况下,数据无法放入单个块中,ClickHouse 会以多个块的形式发送数据。 请看下面这个查询:它会拉取两行数据,并通过减小块大小来强制将数据拆分为每块一行:
输出结果:

简单数据类型

这些较简单的数据类型中,单个值的传输格式与 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 数据类型传输格式”中上述类型的说明。

复杂数据类型

以下类型的编码格式与 RowBinaryRowBinaryWithNamesAndTypes 不同。
  • Nullable
  • LowCardinality
  • Array
  • Map
  • Variant
  • Dynamic
  • JSON

Nullable

Native 格式中,可空列在实际数据之前会有一段字节数据,其字节数等于块中的行数。每个字节都表示对应的值是否为 NULL。例如,在这个查询中,每个奇数都会改为 NULL
输出如下所示:
Nullable(String) 的工作方式也类似。NULL 指示始终来自 nullable 掩码字节—— 掩码值为 0x01 表示该行是 NULL,与字符串内容无关。对于 NULL 行, 底层字符串存储为空字符串 (LEB128 长度为 0) 。请注意,非 NULL 的空 字符串其 LEB128 长度也为 0,因此只有掩码字节才能区分这两种情况。例如,以下查询:
输出将类似如下:

LowCardinality

不同于在 RowBinaryLowCardinality 是透明的,Native format 使用基于字典的列式编码。一个列会被编码为版本前缀,随后是唯一值字典,以及一个指向该字典的整数索引数组。
列可以定义为 LowCardinality(Nullable(T)),但不能定义为 Nullable(LowCardinality(T)) —— 这始终会导致 server 返回错误。
版本前缀是一个值为 1UInt64(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 宽度对索引值进行批量序列化。
字典始终在索引 0 处包含一个默认值 (例如,String 的空字符串、数值类型的 0) 。对于 LowCardinality(Nullable(T)),索引 0 表示 NULL,并且这些键在序列化时不带 Nullable 包装。 例如,LowCardinality(String) 有 5 行 ['foo', 'bar', 'baz', 'foo', 'bar']
对于 LowCardinality(Nullable(String))NULL 对应的索引为 0:

Array

RowBinary 为每个数组都添加 LEB128 元素计数前缀不同,Native format 会将数组编码为两个列式子流:
  • N 个累计的 UInt64 偏移量 (小端序,每个 8 字节) 。第 i 行有 offset[i] - offset[i-1] 个元素,其中 offset[-1] 默认视为 0。
  • 所有行中的全部嵌套元素会被连续地批量序列化。
例如,Array(UInt32) 有 3 行 [[0, 10], [1, 11], [2, 12]]
空数组与前一行的偏移量相同。例如,包含 4 行 [[], ['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

RowBinary 不同,后者每一行都会携带各自的判别字节,并在后面内联存放对应的值;而 Native format 会将判别值与数据分开存储。
与 RowBinary 一样,定义中的类型始终按字母顺序排序,判别值则是该有序列表中的索引。0xFF (255) 表示 NULL
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

RowBinary 中每个值都是自描述的 (类型前缀 + 值) 不同,Native format 会将 Dynamic 序列化为结构前缀,后接一个 Variant 列。 结构前缀包含一个 UInt64(LE) 序列化版本,随后是动态类型的数量 (以 VarUInt 表示) ,再后面是以字符串形式表示的类型名称。在 V1 版本中,出于兼容性考虑,类型数量会写入两次。后续数据是一个 Variant 列,其类型列表由这些动态类型以及一个内部 SharedVariant 类型组成,并按字母顺序排序。 例如,包含 5 行 [0::UInt32, 'hello', NULL, 3::UInt32, 'hello']Dynamic

JSON

RowBinary 不同,后者的每一行都包含路径名和值,因此是自描述的;而 Native format 会以列式结构来序列化 JSON。这种编码较为复杂,并且依赖于版本:它由一个结构前缀组成,其中包含序列化版本、动态路径名称和共享数据布局;随后依次是类型化路径 (每个都作为一整列) 、动态路径 (每个都作为一个 Dynamic 列) ,以及用于溢出路径的共享数据。 为了获得更简单的互操作性,可以考虑使用设置 output_format_native_write_json_as_string=1,它会将 JSON 列序列化为普通的 JSON 文本字符串 (每行一个 String) 。
最后修改于 2026年6月25日