Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

原生协议

原生协议是 ClickHouse 客户端和服务器通过 TCP 进行通信时使用的二进制、面向连接的协议。它承载 SQL 查询、结果数据、INSERT 载荷、执行遥测以及错误信号。它也是命令行客户端、C++ 以及大多数第三方原生驱动所使用的底层协议。

本页介绍协议本身:数据包分帧、连接状态机、版本协商,以及每一种非 Block 消息的消息体。Data 家族数据包中的字节 (即 Block、其列以及各类型的编码) 属于另一部分内容,记录在 Native Format 规范中。

该协议始终具有以下几个特性:它是二进制的,并且按位置解析;除 BlockInfo 内部外,没有字段标签,因此只要有一个字节错位,后续所有内容都会失去同步。它是有状态的,并且每个 TCP 连接一次只处理一个查询——不存在多路复用。定长整数采用小端序。

概览

属性
传输 TCP,可选择使用 TLS 封装
字节序 定长整数采用小端序
编码 二进制和位置编码 (BlockInfo 除外,不含字段标签)
连接模型 有状态,一次只处理一个查询,不支持多路复用
版本机制 在握手期间协商;各项功能是否可用由版本决定
数据格式 所有表格数据均使用 Native Format

在线上传输的每条消息都以一个 VarUInt 数据包类型编码开头,后面跟着一个消息体,其形态取决于该编码以及协商出的协议版本。

一个连接会经历三个阶段——先进行一次性握手,然后进行任意次数的 PingQuery 交换,最后关闭:

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: TCP connect

    rect rgb(220, 235, 255)
        Note over C,S: Handshake
        C->>S: ClientHello (name, version, db, user, password)
        S->>C: ServerHello (server_name, version, [timezone, display_name, ...])
        Note over C,S: negotiated_version = min(client, server)
        opt negotiated_version ≥ 54458
            C->>S: Addendum (quota_key)
        end
    end

    rect rgb(220, 245, 225)
        Note over C,S: Query phase
        C->>S: Query packet (ClientInfo, settings, params, SQL)
        C->>S: External-table Data packets (0 or more)
        C->>S: Empty Data marker — the "go" signal
        loop until EndOfStream or Exception
            S->>C: Data / Progress / Log / ProfileInfo / Totals / ...
        end
        S->>C: EndOfStream
    end

原生 TCP 协议始终以 Native 格式传输表格数据,而不会理会 SQL 中任何 FORMAT 子句。重新格式化为 RowBinaryCSVJSON 等格式是客户端的工作,这一步会在其解码 Native 块之后完成。 (HTTP interface 则是另一条代码路径,确实会遵循 FORMAT 子句;这里不作讨论。)

安全

传输安全 (TLS)

TLS 位于传输层,处于协议层之下。启用后,整个 TCP 数据流都会被加密;而无论是否使用 TLS,协议消息在字节级别上都完全一致。

身份验证

身份验证在握手阶段进行,也就是在 ClientHello 消息中完成。userpassword 字段会以明文字符串形式传输,因此需要依靠传输层加密 (TLS) 来保护传输中的凭据。

服务器会将空的 user 字段解释为其配置的默认会话用户:即 default_session_user 服务器设置 (其默认值为 default) ,也可能在 protocols 部分中按 listener 被覆盖。如果服务器配置的默认会话用户为空,或者服务器版本早于 26.8,则会以异常拒绝空的 user 字段。请注意,clickhouse-client 从不发送空用户名:它会在客户端将其替换为 default

从协议版本 54466 开始,支持 SSH 质询-响应身份验证——请参阅 SSH 质询-响应身份验证

服务器间密钥

对于分布式查询执行,服务器之间会通过证明自己知晓某个共享密钥来相互进行身份验证,而无需在传输过程中直接发送该密钥。每个 Query 都会在 Query 的字段 4 中携带一个 32 字节的 SHA-256 auth_hash,它根据 salt、nonce、已配置的密钥以及查询内容计算得出,接收服务器会重新计算并进行比对。此功能受 INTERSERVER_SECRET 功能开关 (v54441) 控制。外部客户端在此处始终发送空字符串。请参阅服务器间身份验证

版本控制与功能开关

版本协商

客户端和服务端都会在握手期间声明其支持的最高协议版本。协商后的版本取两者中较小者:

negotiated_version = min(client_version, server_version)

此后的每条消息都会使用协商出的版本来决定传输时包含哪些字段。

功能开关

每项功能都以引入它的协议版本作为标识;当协商出的版本大于或等于该版本号时,该功能即处于生效状态。

功能列表

特性 版本 影响 线协议影响
BLOCK_INFO all Block 为每个 Block 添加 BlockInfo 前缀 (is_overflowsbucket_number) 。
CLIENT_INFO 54032 Query 将 ClientInfo 块添加到 Query 数据包主体中。
TIMEZONE 54058 ServerHello 在 ServerHello 中添加 timezone 字段。
QUOTA_KEY_IN_CLIENT_INFO 54060 ClientInfo 在 ClientInfo 中添加 quota_key 字段。
DISPLAY_NAME 54372 ServerHello 在 ServerHello 中添加 display_name 字段。
VERSION_PATCH 54401 ServerHello, ClientInfo 在两者中都添加 version_patch 字段。
SERVER_LOGS 54406 Log 设置 send_logs_level 后,server 会发送日志数据包。
COLUMN_DEFAULTS_METADATA 54410 TableColumns server 可能会在 INSERT/输入 schema 块之前发送 TableColumns 数据包 (类型 11) ,其中包含列默认值元数据。仅当协商版本 ≥ 54410 启用了 input_format_defaults_for_omitted_fields 时才会发送。低于此版本时,该数据包绝不会发送;client 不得等待它。
WRITE_CLIENT_INFO 54420 Progress 在 Progress 中添加 wrote_rowswrote_bytes。 (尽管名字如此,这不会控制 ClientInfo 块——控制它的是 CLIENT_INFO (v54032) 。)
SETTINGS_SERIALIZED_AS_STRINGS 54429 Query (settings encoding) 更改始终存在的 settings 列表的编码方式不会控制是否发送 settings。v54429+ 会将每个 setting 编码为 (name, flags, value-as-string);旧版本对端则编码为 (name, type-specific-binary-value),且不包含 flags。参见 Setting
INTERSERVER_SECRET 54441 Query 在 Query 中添加 inter-server auth_hash 字段——它是对集群 secret 加盐后的 SHA-256,而不是原始 secret。外部 client 会发送空字符串。参见 Inter-server authentication
OPEN_TELEMETRY 54442 ClientInfo 在 ClientInfo 中添加 OpenTelemetry trace context。
DISTRIBUTED_DEPTH 54448 ClientInfo 在 ClientInfo 中添加 distributed_depth 字段。
INITIAL_QUERY_START_TIME 54449 ClientInfo 添加 initial_time 字段 (Int64,固定宽度) 。
PROFILE_EVENTS 54451 ProfileEvents server 会在查询执行期间发送 ProfileEvents 数据包。
PARALLEL_REPLICAS 54453 ClientInfo 在 ClientInfo 中添加并行副本协调字段。
CUSTOM_SERIALIZATION 54454 Block (Column) 在每列的类型字符串后添加 has_custom_serialization 字节。
ADDENDUM 54458 Handshake client 会在握手交换后发送附加信息 (quota_key) 。
PARAMETERS 54459 Query 将参数列表添加到 Query 数据包主体中。
SERVER_QUERY_TIME_IN_PROGRESS 54460 Progress 在 Progress 中添加 elapsed_ns 字段。
PASSWORD_COMPLEXITY_RULES 54461 ServerHello 在 ServerHello 中添加密码策略正则模式列表和人类可读消息。
INTERSERVER_SECRET_V2 54462 ServerHello 在 ServerHello 中添加一个 8 字节的 UInt64 nonce。用于 inter-server 查询签名;外部 client 会解码并忽略它。
TOTAL_BYTES_IN_PROGRESS 54463 Progress 在 Progress 中添加 total_bytes_to_read (VarUInt) 字段,位置在 total_rowswrote_rows 之间。
TIMEZONE_UPDATES 54464 TimezoneUpdate 添加 TimezoneUpdate server 数据包 (类型 17) 。主体:单个 String,携带会话时区。仅由 input table function 初始化器发送,紧跟在输入 schema 块之后,以便 client 使用 server 的 session_timezone 解析其发送的行。参见 TimezoneUpdate
SPARSE_SERIALIZATION 54465 Block (Column) server 可将 has_custom_serialization 设为 1,并发送稀疏编码列。传输格式:1 字节 kind (0x01 = SPARSE) ,然后是以 EOG 结束的 VarUInt 偏移流,接着是按内部类型密集编码的非默认值。参见 kind_stack and sparse encoding
SSH_AUTHENTICATION 54466 Auth flow 添加 SSH 质询-响应身份验证。可选启用:client 发送形如 " SSH KEY AUTHENTICATION " + <real_user>user,并使用空密码触发该流程。参见 SSH challenge-response authentication
TABLE_READ_ONLY_CHECK 54467 TablesStatusResponse 在 TablesStatusResponse 中为每个表对应的行添加 is_readonly flag。不会发出 TablesStatusRequest 的外部 client 不会看到任何传输格式变化。
SYSTEM_KEYWORDS_TABLE 54468 system tables server 会填充 system.keywords,以便规范的 clickhouse-client 可以自动补全关键字。native-protocol 线协议没有变化。
ROWS_BEFORE_AGGREGATION 54469 ProfileInfo 按此顺序在 ProfileInfo 末尾添加 applied_aggregation (Bool) 和 rows_before_aggregation (VarUInt) 。
CHUNKED_PROTOCOL 54470 Connection framing 按数据包分块的分帧机制会包装每个数据包主体。在 Addendum 中协商。ServerHello 携带 server 对每个方向的偏好;Addendum 携带 client 的最终选择。参见 chunked framing
VERSIONED_PARALLEL_REPLICAS_PROTOCOL 54471 ServerHello, Addendum 双方会交换一个 VarUInt 并行副本协调协议版本。ServerHello 中该字段位于 紧接 protocol_version 之后 (在 timezone 之前) 。Addendum 中该字段追加在分块协议字符串之后。当前值:8 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION) 。版本 8 新增 MergeTreeAllRangesAnnouncementResponse (client 数据包 14):当协商后的并行副本版本为 ≥ 8 时,发起方会针对每个非 Default 模式的 follower 通知,回复该 stream 的权威 parts 列表,而 follower 会在发出读取请求前等待该回复。低于 8 时,通知是发出即忘的。
INTERSERVER_EXTERNALLY_GRANTED_ROLES 54472 查询 在 Query body 中,于 settings 终止符和 interserver-secret 哈希之间新增一个 String external_roles 字段。外部 client 发送空角色列表 (单个字节 0x00,即 String 封装中的 VarUInt 0) 。
V2_DYNAMIC_AND_JSON_SERIALIZATION 54473 列 body server 可能会对 DynamicJSON 列类型输出 V2 serialization——这决定了它们使用哪个 state_prefix version。参见版本化类型
SERVER_SETTINGS 54474 ServerHello server 会在 ServerHello 尾部的 nonce 之后,以列表形式广播其非默认 settings。格式为以空 key 结尾的 (key, flags, value) 三元组——与 Query packet 的 settings 列表相同。
QUERY_AND_LINE_NUMBERS 54475 ClientInfo 在 ClientInfo 尾部新增 script_query_number (VarUInt) 和 script_line_number (VarUInt) 。由 clickhouse-client 用于多 statement 脚本的错误定位;外部 client 发送 0, 0
JWT_IN_INTERSERVER 54476 ClientInfo 在 ClientInfo 尾部新增一个表示 JWT 是否存在的 UInt8,以及可选的 String jwt。外部 client (无 JWT) 发送字节 0x00。 (在 C++ 中拼写为 DBMS_MIN_REVISON_WITH_JWT_IN_INTERSERVER——注意该常量名中的拼写错误。)
QUERY_PLAN_SERIALIZATION 54477 ServerHello, QueryPlan packet ServerHello 会在 server settings 之后追加 VarUInt query_plan_serialization_version。同时引入 ClientPacket::QueryPlan (代码 13) ,用于 server 间传递预构建的查询计划——外部 client 不会发送。
PARALLEL_BLOCK_MARSHALLING 54478 块 (列) server 可能会将列包装在 ColumnBLOB (内联压缩) 中进行并行处理。其启用条件是查询启用了 compression 且 rows > 1;否则仍使用常规的列传输格式。对于从不在传出 Query packet 上启用 compression 的 client,不会看到传输格式变化。
VERSIONED_CLUSTER_FUNCTION_PROTOCOL 54479 ServerHello 在 ServerHello 尾部新增 VarUInt cluster_function_protocol_version。用于 *Cluster table function (s3Cluster 等) 。当前值:8 (DBMS_CLUSTER_PROCESSING_PROTOCOL_VERSION);版本 7 为私有仓库特性 (Iceberg compaction) 预留,而 8 会向 inter-server cluster 读取任务载荷可选添加一个 read_source_index (ReadTaskResponse body,此处仍未具体说明——见下文)。外部 client 会解码后忽略。
OUT_OF_ORDER_BUCKETS_IN_AGGREGATION 54480 BlockInfo 在 BlockInfo 的带字段标签 stream 中新增字段 3 (out_of_order_buckets: Vec<Int32>) 。解码方式为 [VarUInt count][Int32]*count。外部 client 自身不会发出该字段;解码器会读取 server 发送的任何非空列表。
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS 54481 日志, ProfileEvents, TableColumns server 可能会将 LogProfileEventsTableColumns packet body 包装在压缩帧中。在此版本中,这三种 body 都通过同一条可选压缩的输出路径传输,且只有当查询设置了 compression = true 时,才会真正使用压缩帧。对于从不在传出 Query packet 上启用 compression 的 client,不会看到传输格式变化。
REPLICATED_SERIALIZATION 54482 块 (列) server 可能会输出 kind_stack 为 0x04 = REPLICATED 的列——这是一种针对重复值的字典式紧凑表示——参见kind_stack 与稀疏编码。低于此版本时,写入器会在发送前展开这类列。通过索引查找解码 (每行 elements[indexes[i]]) ;支持叶子类型以及 Nullable/Array/Tuple/Map/Nested/LowCardinality 内部类型。
NULLABLE_SPARSE_SERIALIZATION 54483 块 (列) 将稀疏 serialization 与 Nullable(T) 组合使用。低于此版本时,写入器会在发送前为 Nullable 列展开稀疏表示;在 v54483+ 中,传输数据为 sparse-over-Nullable。参见kind_stack 与稀疏编码
PROGRESS_IN_ASYNC_INSERT 54484 Progress (INSERT) 对于异步 INSERT (async_insert = 1) ,一旦 insert 被刷新,server 会在 EndOfStream 之前额外发送一个 Progress packet,然后发送该 insert 的 ProfileEvents。其启用条件是协商后的 version ≥ 54484;低于该版本时,server 会省略这个尾随的 Progress。Progress 的传输格式本身没有变化——新增的只是发送行为。实际中,该增量承载的是耗时;写入行计数器则通过随附的 ProfileEvents 报告。已经能够处理交错 Progress 的 client 无需修改格式,只需容忍多出一个 packet。
CLIENT_AGENT_IN_CLIENT_INFO 54485 ClientInfo 在 ClientInfo 尾部新增一个 client_agent String。规范 client 会自动从其环境中检测 agent 标识符 (例如 claude-codecursorgemini-cli,或 AGENT 变量的值) ;如果外部 client 未检测到任何值,则发送空字符串。一旦协商 version ≥ 54485,该字段就是必需的——省略它会导致 Query packet 其余部分失去同步。
INTERNAL_QUERY_FLAG 54486 ClientInfo 在 ClientInfo 尾部新增一个 is_internal UInt8。对于 server 内部查询 (而非用户发起的查询) ,其值为 1,并会传播到远程查询,从而使它们在 system.query_log 中的行被标记为 internal;外部 client 发送 0。一旦协商 version ≥ 54486,该字段就是必需的——省略它会导致 Query packet 其余部分失去同步。
INTERSERVER_CURRENT_ROLES 54488 ClientInfo 在 ClientInfo 尾部新增一个可选的 current_roles String 列表 ([UInt8 present],若为 1,则为 [VarUInt count][String]*count) ;对于 inter-server 查询,还会扩展 auth_hash 以涵盖序列化后的列表。该列表携带发起方当前处于活动状态 (已启用) 的角色名称,使辅助节点能以相同方式应用行策略,而不是回退到用户的默认角色。仅对 inter-server 查询填充,且仅当 push_external_roles_in_interserver_queries = 1 时填充;外部 client 发送 0 (不存在) 。一旦协商 version ≥ 54488,该字段就是必需的——省略存在性字节会导致 Query packet 其余部分失去同步。
CURRENT_AGGREGATION_VARIANT_SELECTION_METHOD 54489 聚合 (两级桶) 单个 String key 的聚合方法遵循 enable_packed_string_keys_in_aggregation。低于此修订版本的对端始终使用 packed 方法,且不识别该 setting,因此与其交换两级桶会使相同的 key 被放入不同的桶。对于此类对端,发起方会以安全失败方式处理:将其两级阈值设为零,并自行重新对其单级块进行分桶。比较的是对端的修订版本而非 version,因为该方法可在同一发行版本内变更。native-protocol 线协议没有变化。
HTTP_HANDLER_IN_CLIENT_INFO 54490 ClientInfo 在 ClientInfo 的 HTTP 分支中,紧接 http_referer 之后新增 http_handler_namehttp_request_url (均为 String)。它们携带匹配到的 SQL 定义 HTTP handler 名称和请求 URL,以便分布式 handler 查询在远程分片上的 currentHandler/currentRequestURL 以及 system.query_loghttp_handler_name/http_request_url 列保持填充。仅当 query_interface = HTTP 时写入 (非 handler HTTP 查询则写入空字符串);一旦协商 version ≥ 54490,该字段就是必需的——省略它会导致 Query packet 其余部分失去同步。
QUANTILE_DETERMINISTIC_SKIP_DEGREE 54491 块 (列) AggregateFunction(quantileDeterministic, ...) (以及其 quantiles/median 拼写形式) 的状态版本从 0 提升至 1,从而在每个序列化状态末尾追加一个 UInt8 skip_degree。仅该聚合函数的状态载荷发生变化;列分帧保持不变,其他所有聚合函数仍保留各自的版本。低于此修订版本时,写入器会输出版本 0 状态,因此不会影响旧对端。
STRING_WITH_SIZE_STREAM_SERIALIZATION 54492 块 (列) String 列从逐值长度前缀布局切换为独立的累计字节偏移流 (与 Array 使用的偏移布局相同) ,按原样发送:[UInt64 × num_rows offsets][data blob]。由协商后的修订版本控制;低于 54492 时,写入器使用逐值长度前缀。线协议不携带此项的逐列标记——双方仅根据修订版本确定。参见 String 列布局

数据包封装

在线上传输的每条消息,无论哪个方向,其外层结构都相同:

[VarUInt: packet_type_code]    always encoded as VarUInt
[message body]                 format depends on packet_type_code

完整的数据包类型表见数据包类型参考

数据包类型是 VarUInt,而不是定宽字节。对于小于 128 的值,VarUInt 产生的仍然是相同的单个字节,但实现必须使用 VarUInt 编码,以确保当未来的数据包类型达到 128 或更大时仍能保持兼容。

消息参考仅说明每个数据包的 包体 —— 即位于数据包类型代码之后的字节。字段编号从 1 开始,包体中的第一个字段编号为 1。

分块帧封装 (v54470+)

CHUNKED_PROTOCOL 功能协商完成后 (参见握手) ,线路上传输的每个数据包都会使用分块帧进行封装。这种封装是按方向分别进行的:client→server 和 server→client 会分别协商,最终可能采用不同的模式 (分块或无帧封装) 。

每个数据包在线路上的布局:

<chunk>...   one or more chunks; their payloads concatenated form the whole packet
[u32 LE = 0] zero-size terminator marking end of packet

每个 chunk 的线格式:

[u32 LE: chunk_size]   chunk_size in [1, UINT32_MAX]
[chunk_size bytes]     packet bytes (see note below)

VarUInt 数据包类型位于分块流 内部:它是数据包载荷的第一个字节 (即第一个 chunk 的第一个字节) ,而不是在分帧之前单独提前发送的一个字节。每个数据包的 chunk 载荷都是来自数据包封装的完整 [VarUInt packet_type_code][message body]。如果客户端把数据包类型放在分块流之外,对端就会把这个类型字节当作 u32 chunk 大小的第一个字节来读取,导致连接失去同步。

如果写入端的缓冲区在数据包中途写满,单个数据包可以拆分到多个 chunk 中;拆分点可以出现在任何位置,包括数据包类型的 VarUInt 内部。读取端会拼接各个 chunk 载荷,并将末尾的 4 字节零值视为透明的数据包边界——它会将其消费掉,但不会把它暴露给负责读取数据包消息体的逻辑。

没有消息体的数据包仍然会被封装:像 PingPong 这样的单字节数据包,在协商启用分块后会变成 [u32 size = 1][0x04][u32 0]。本页其他地方任何“在线路上是单字节”的描述,指的都是分块前的形式。

协商。 ServerHello 和 Addendum 各自携带两个 String 字段,每个方向一个,取值来自 {"chunked", "notchunked", "chunked_optional", "notchunked_optional"}

  • chunked / notchunked 是严格模式:该方向要求必须精确使用该模式。
  • _optional 变体是灵活的:它们接受对端选择的任意模式。

每个方向的最终协商值按双方成对计算:

Server pref Client pref Agreed
*_optional anything 跟随 CLIENT (即其 starts_with("chunked"))
anything *_optional 跟随 SERVER
chunked strict chunked strict chunked
notchunked strict notchunked strict notchunked
strict mismatch strict mismatch 协议错误 —— 该连接 MUST 被断开

在客户端一侧,客户端的 SEND 偏好会与服务端的 RECV 偏好协商,反之亦然。

时序。 这些协商字符串通过未分帧的线路传输:ClientHello → ServerHello (服务端偏好) → Addendum (客户端的协商结果值) 。分帧模式切换适用于 Addendum 被刷出 之后 发送的每一个字节。Addendum 本身、ClientHello 和 ServerHello 始终不分帧。

连接生命周期

在任何时刻,连接都只会处于以下四种状态之一:HANDSHAKEREADYREADING_RESPONSE,或已终止。由于该协议不支持多路复用,如果客户端在尚未读取完上一个响应之前就发送新请求,就会导致传输中的字节交错,从而破坏数据流。

状态

stateDiagram-v2
    [*] --> HANDSHAKE: TCP connect

    HANDSHAKE --> READY: handshake ok
    READY --> READING_RESPONSE: send Query
    READING_RESPONSE --> READY: EndOfStream / Exception
    READY --> READY: Ping / Pong

    HANDSHAKE --> Terminated: handshake error
    READING_RESPONSE --> Terminated: protocol violation / I/O error
    READY --> Terminated: close
    Terminated --> [*]

顺畅路径沿直线向下推进——HANDSHAKE → READY → READING_RESPONSE → READY——其中 Ping/Pong 会形成自循环,而所有失败分支最终都会汇入唯一的 Terminated 终态。

State Description
HANDSHAKE TCP connection 打开后的初始状态。只有握手消息是有效的。成功时转换到 READY,失败时则终止。
READY 空闲。客户端可以发送 PingQuery,或关闭连接。该连接可以无限期停留在 READY 状态 (受 idle_connection_timeout 限制,参见连接限制) 。
READING_RESPONSE 当客户端发送 Query 时进入此状态。客户端必须先完整读取服务器的响应 stream,才能返回 READY。此时唯一允许的 client→server 数据包是 Cancel (本页未说明) 。
Terminated 不再可用。客户端必须打开新的 TCP connection 并重新开始握手。

握手阶段

进行身份验证并协商协议版本。每个连接只会发生一次,并且先于任何其他操作。

TCP 连接刚刚建立,双方尚未交换任何消息。流程如下:

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: ClientHello
    alt success
        S->>C: ServerHello
    else error
        S->>C: Exception — connection terminates
    end
    Note over C,S: negotiated_version = min(client, server)
    opt negotiated_version ≥ 54458
        C->>S: Addendum
    end
  1. 客户端发送 ClientHello,其中包含其支持的最高协议版本。

  2. 客户端读取响应,并根据数据包类型进行分发处理:

    数据包类型 操作
    Hello (0) 解码 ServerHello。计算 negotiated_version = min(client_ver, server_ver)。继续执行步骤 3。
    Exception (2) 解码 Exception。将其作为错误返回,并终止连接。
    anything else 违反协议。终止连接。
  3. 如果 negotiated_version ≥ 54458 (ADDENDUM 功能) ,客户端会发送一个 Addendum。这一决定基于协商后的版本,而不是客户端声明的版本。

成功时,连接会进入 READY;发生任何错误时,连接都会终止。

Ping 阶段

一种应用层的存活检查,独立于 TCP keepalive。成功完成一次 Ping/Pong 往返即可确认 TCP 连接在两个方向上都保持存活,并且服务器能够正常响应。Ping 是无状态的,与任何查询都不关联,因此多个连续的 Ping 彼此独立。

READY 开始,流程如下:

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: Ping (0x04)
    alt responsive
        S->>C: Pong (0x04)
    else error
        S->>C: Exception
    end
  1. 客户端发送 Ping

  2. 客户端读取响应:

    数据包类型 操作
    Pong (4) 确认存活,返回到 READY
    Exception (2) 解码 Exception,并将其作为错误返回。
    其他任何类型 协议违规。

查询阶段

客户端提交一条 SQL 语句;服务器以流式方式返回结果块和执行遥测信息。响应由一系列数据包组成,并且恰好以一个 EndOfStreamException 结束。

READY 开始,流程如下:

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: Query message
    C->>S: External-table Data packets (0 or more)
    C->>S: Empty Data marker — end-of-client-data (required)
    S->>C: Data — header block (N cols, 0 rows)
    loop until EndOfStream or Exception
        S->>C: Progress / Log (interleaved)
        S->>C: Data — result block (N cols, M rows)
        S->>C: Totals / Extremes (aggregation queries)
        S->>C: ProfileInfo / ProfileEvents (profiling)
    end
    S->>C: Data — empty block (boundary marker)
    S->>C: Progress — final updates
    S->>C: EndOfStream — authoritative end of query

如果在任意阶段发生错误,服务端会发送 Exception 而不是 EndOfStream,从而终止查询。

  1. 客户端发送带有唯一 query_id (通常为 UUID) 的 Query

  2. 客户端发送所有外部表,然后发送空的 Data 标记。空 Data 数据包的字段为 table_name = ""num_columns = 0num_rows = 0。服务端在收到此标记之前不会开始执行查询。

  3. 客户端进入 READING_RESPONSE,并刷写其写入缓冲区。

  4. 客户端在循环中读取响应数据包,并按类型分发处理:

    数据包类型 Action
    Data (1) 解码该块。第一个 Data 是 schema 头;后续的是结果块 (累积) ;空块是边界标记。num_rows == 0 不是 查询结束。
    Progress (3) 执行指标。每个数据包都是相对于前一个数据包的增量——在本地累积。
    EndOfStream (5) 查询完成。退出循环并返回 READY
    ProfileInfo (6) 执行后的 profiling 数据。
    Totals (7) aggregation totals 块 (与 Data 相同的传输格式) 。
    Extremes (8) 最小/最大值块 (与 Data 相同的传输格式) 。
    Log (10) 服务端日志行。
    TableColumns (11) 列默认值 metadata。
    ProfileEvents (14) 性能计数器。
    Exception (2) 解码后作为错误返回。退出循环并返回 READY
    anything else 在查询阶段属于异常情况。终止连接。

收到 EndOfStream 或已处理的 Exception 后,连接会返回 READY。如果发生协议违规或 I/O 错误,连接会被终止。

INSERT 阶段

INSERT 阶段是在查询阶段的基础上增加了两次额外的交互。客户端提交一条 INSERT 语句;服务器返回一个描述目标表的 schema 块;客户端随后以流式方式发送包含这些行的 Data packets,再发送空的 Data 标记;最后,服务器以 EndOfStreamException 结束。

READY 状态开始,SQL 采用如下形式的 INSERTINSERT INTO <table> [(<cols>)] VALUES —— 不包含内联的 VALUES (...) 字面量,因为行数据是通过 Data packets 传输的。流程如下:

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: Query packet (INSERT body)
    C->>S: External-table Data packets (0 or more)
    Note over C,S: No empty Data marker here —<br/>it would end the row stream before it starts
    opt metadata before schema
        S->>C: TableColumns / Progress / ...
    end
    S->>C: Data packet — schema block (columns, 0 rows)
    Note over C,S: Schema block is the contract:<br/>rows sent next must match these column shapes
    loop one or more blocks
        C->>S: Data packet (rows N)
    end
    C->>S: Data packet — empty block (0 rows), end-of-input terminator
    loop until EndOfStream or Exception
        S->>C: Progress / ProfileInfo / Log / ProfileEvents
    end
    opt async_insert = 1 and negotiated_version ≥ 54484
        S->>C: trailing Progress, then insert ProfileEvents
    end
    S->>C: EndOfStream
  1. 客户端发送 查询,并将 body 设为 INSERT SQL。
  2. 客户端发送所有外部表 (这种情况在 INSERT 中较少见) 。与 查询 phase 不同,这里不会发送空的 Data 标记。INSERT查询 数据包会连同待发送的数据一起发出,因此表示数据结束的空数据块会推迟到步骤 5;如果在 schema 块之前发送它,服务器会将其视为行流结束,从而以 0 行完成 INSERT,随后再把第一个真实的行数据包解析为一个游离的顶层数据包。
  3. 客户端持续读取元数据包 (TableColumns、Progress、ProfileInfo、Log、ProfileEvents) ,直到读到 schema Data 数据包——这是一个 0 行但包含完整列结构 (名称和类型) 的 Block。schema 块就是约定:客户端接下来发送的行必须符合这些列的形态。
  4. 客户端发送一个或多个数据块。对于每个块,它都会先写入 VarUInt(ClientPacket::Data = 2),然后写入表示空外部表名称的 String(""),接着写入 Block。列类型必须按位置与 schema 块中的列对齐。
  5. 客户端发送输入结束标记:一个带空 Block (0 列、0 行) 的 Data 数据包。
  6. 客户端持续读取响应流,直到 EndOfStream (成功) 或 Exception (失败) 。

异步 INSERT (v54484+) 。 当查询带有 async_insert = 1 时,服务器会将这些行放入队列,并作为某个批次的一部分进行刷写。在协商版本 ≥ 54484 (PROGRESS_IN_ASYNC_INSERT) 时,一旦刷写完成,服务器会额外发出一个 Progress 数据包,紧接着发送该次 insert 的 ProfileEvents,然后是 EndOfStream。在 54484 以下,服务器会跳过这个尾部的 Progress。该数据包是一个普通的 Progress;由于服务器在合并写入计数前会重置查询管道,因此其中的增量实际上只包含已用时间,而写入行数和字节统计则通过随附的 ProfileEvents 传递给客户端。对于已经在步骤 6 中处理交错 Progress 的客户端,只需再接受一个额外的数据包即可。

连接在收到 EndOfStream 或已处理的 Exception 后会返回 READY。协议违规和 I/O 错误会终止连接。

消息参考

各字段按 wire 顺序列出。Type 列使用:

  • VarUInt — 可变长度无符号整数 (参见 VarUInt) 。
  • String — 以 VarUInt 为前缀的字节序列 (参见 String) 。
  • UInt8Int32 等 — 固定宽度的小端序整数。
  • Bool — 单个字节,0x000x01

Role 列说明每个字段由谁使用:

  • client — 由外部客户端设置。
  • inter-server — 仅对服务器之间的通信有意义;外部客户端写入默认值。
  • universal — 两者都会使用。

这些表仅记录每个数据包的包体,即位于数据包类型代码之后的部分。

ClientHello (数据包类型 0)

客户端 → 服务器。TCP 连接建立后发送的第一条消息。

# 字段 类型 角色 描述
1 client_name String 通用 客户端标识符 (例如 "clickhouse-client")
2 version_major VarUInt 通用 客户端主版本号
3 version_minor VarUInt 通用 客户端次版本号
4 protocol_version VarUInt 通用 客户端支持的最高协议版本
5 database String 通用 默认数据库名称
6 user String 通用 用于身份验证的用户名。留空表示服务器的默认会话用户 (default_session_user 服务器设置;低于 26.8 的服务器会拒绝) 。请参阅身份验证
7 password String 通用 密码 (明文)

ServerHello (数据包类型 0)

服务器 → 客户端。对 ClientHello 在身份验证成功后的响应。

# Field Type Role Condition Description
1 server_name String universal always Server 标识符
2 version_major VarUInt universal always Server 主版本号
3 version_minor VarUInt universal always Server 次版本号
4 protocol_version VarUInt universal always Server 的协议版本
4a parallel_replicas_protocol_version VarUInt universal VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) Server 的并行副本协调协议版本。Wire 位置:紧接在 protocol_version 之后,位于 timezone 之前。当前值:8
5 timezone String universal TIMEZONE (v54058) 服务器时区 (例如 "UTC")
6 display_name String universal DISPLAY_NAME (v54372) 便于人类阅读的 Server 名称
7 version_patch VarUInt universal VERSION_PATCH (v54401) Server 补丁版本号
8 proto_send_chunked_srv String universal CHUNKED_PROTOCOL (v54470) Server 首选的 Outbound 分块方式。可取值为 "chunked""notchunked""chunked_optional""notchunked_optional"。参见分块成帧尽管它的版本门槛更高,但在 wire 上位于 password_complexity_rules 之前。
9 proto_recv_chunked_srv String universal CHUNKED_PROTOCOL (v54470) Server 首选的 Inbound 分块方式。取值与字段 8 相同。
10 password_complexity_rules Rule[] universal PASSWORD_COMPLEXITY_RULES (v54461) Server 的密码策略。格式为 VarUInt count,后跟 count × Rule。见下文。
11 nonce UInt64 inter-server INTERSERVER_SECRET_V2 (v54462) 8 字节 LE 随机 nonce。Server 的 inter-server 查询签名方案会使用它。外部客户端 MUST 对其进行解码 (以保持 stream 对齐) ,并且 SHOULD 忽略该值。
12 server_settings Setting[] universal SERVER_SETTINGS (v54474) Server 广播的非默认 Settings。格式:零个或多个 (String key, VarUInt flags, String value) 三元组,以空 key 结束。与 Query packet 的设置列表相同。
13 query_plan_serialization_version VarUInt universal QUERY_PLAN_SERIALIZATION (v54477) Server 支持的 查询计划 serialization version。外部客户端解码后忽略即可。
14 cluster_function_protocol_version VarUInt universal VERSIONED_CLUSTER_FUNCTION_PROTOCOL (v54479) Server 的 *Cluster 表函数协议版本。当前值:8。该值用于控制 inter-server cluster 读取任务载荷中新增字段的启用 (即原本未作说明的 ReadTaskResponse body);版本 7 为私有 repository 功能 (Iceberg 合并整理) 预留,8 新增了一个可选的 read_source_index。外部客户端不参与 cluster 读取——它们解码后忽略此字段。

Rulepassword_complexity_rules 中的一个元素:

# Field Type Description
1 pattern String 合规密码必须匹配的正则表达式 pattern。
2 message String 密码不符合此规则时显示的便于人类阅读的说明。

该列表反映 server operator 配置的密码策略,仅起提示作用——server 不会在握手期间强制执行这些规则。提供密码修改/设置功能的客户端可利用这些规则,在将不合规密码发送给 server 之前先提示错误。

附加信息 (无数据包类型)

客户端 → 服务器,受 ADDENDUM (v54458) 控制。在握手交换完成后立即发送。它不是一种独立的数据包类型——这些字段会以原始形式直接在传输中发送,前面不带数据包类型字节前缀。

# Field Type Role Condition Description
1 quota_key String universal always 用于服务器端按配额键区分的资源配额。未使用键控配额的客户端会发送空字符串。
2 proto_send_chunked String universal CHUNKED_PROTOCOL (v54470) 客户端协商出的出站分块方式:"chunked""notchunked"。根据 ServerHello 中的 proto_recv_chunked_srv 计算得出。
3 proto_recv_chunked String universal CHUNKED_PROTOCOL (v54470) 客户端协商出的入站分块方式。根据 proto_send_chunked_srv 计算得出。
4 parallel_replicas_protocol_version VarUInt universal VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) 客户端支持的并行副本协调协议版本。不参与分布式查询的外部客户端仍应发送一个有效版本 (当前为 8) ,以便通过服务器的兼容性检查。

分块帧格式的切换会在此附加信息写出后生效——附加信息本身不带帧封装。

Ping (数据包类型 4)

客户端 → 服务器。无消息体——在分块成帧之前,该数据包仅为单个字节 0x04;协商启用分块后,该字节会成为一个块的单字节载荷 (参见 分块成帧) 。

Pong (数据包类型 4)

服务器 → 客户端。无消息体——在采用分块帧之前,该数据包仅为单个字节 0x04;协商启用分块传输后,该字节会作为某个分块的单字节载荷 (参见分块成帧) 。

Exception (数据包类型 2)

服务器 → 客户端。当服务器在任意阶段发生错误时发送。

# 字段 类型 角色 描述
1 code Int32 universal 错误代码
2 name String universal Exception 类 (例如:"DB::Exception")
3 message String universal 人类可读的错误消息
4 stack_trace String universal 服务器端堆栈跟踪
5 has_nested (已废弃) Bool universal 已废弃的兼容性字节。服务器始终将其写为 false

查询 (数据包类型 1)

客户端 → 服务器。

# Field Type Role Condition Description
1 query_id String universal always 唯一查询标识符 (UUID)
2 client_info ClientInfo universal CLIENT_INFO (v54032) 参见 ClientInfo
3 settings Setting[] universal always 参见 Setting始终存在 (以空键结束) ;只有每个 setting 的编码方式受版本限制——参见 Setting 中关于编码的说明。对于协商版本低于 54429 的情况,客户端不得省略此字段。
3a external_roles String universal INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472) 外部授予角色名称列表的序列化结果。空列表 = 字节 0x00 (VarUInt 0) ,并封装在 String 中 (在线路上传输时为 [VarUInt 1][0x00]) 。外部客户端始终发送空列表。
4 auth_hash String inter-server INTERSERVER_SECRET (v54441) 服务器间身份验证哈希——不是原始集群 secret。参见下方的 Inter-server authentication。外部客户端 (以及任何 InitialQuery) 都会发送空字符串。
5 stage VarUInt universal always 查询处理阶段。0 = FetchColumns,1 = WithMergeableState,2 = Complete,3 = WithMergeableStateAfterAggregation,4 = WithMergeableStateAfterAggregationAndLimit,7 = QueryPlan。值 3/4 出现在分布式查询中;7 表示附带一个已序列化的查询计划。外部客户端通常发送 2
6 compression VarUInt universal always 0 = 已禁用,1 = 已启用
7 query_body String universal always SQL 文本
8 parameters Parameter[] client PARAMETERS (v54459) 参见 Parameter。以空键结束。

ClientInfo (嵌入在 查询 中)

客户端 → 服务器,嵌入在 查询 体 (字段 2) 中。受 CLIENT_INFO (v54032) 控制。 (ClientInfo 中的某些字段受更高版本控制,详见下方各字段说明。)

# Field Type Role Condition Description
1 query_kind UInt8 universal always 0 = NoQuery,1 = InitialQuery,2 = SecondaryQuery。外部客户端发送 1
2 initial_user String universal always 发起查询的用户
3 initial_query_id String universal always 原始查询 ID
4 initial_address String universal always 发起端客户端的套接字地址。服务器绝不会解析此值 (不会进行 hostname 或 service-name 查找) 。对于 SECONDARY_QUERY (该值会被保留并使用,例如在 system.query_log 和 inter-server 身份验证中) ,接受的语法是 IPv4 a.b.c.d:port 或带方括号的 IPv6 [addr]:port,其中 host 必须是 IP 字面量,port 必须是 0..65535 范围内的十进制数字;其他形式 (例如 localhost:9000host:http:9000,或 UNIX socket path,如 /tmp/ch.sock) 会因 INCORRECT_DATA 被拒绝。对于 INITIAL_QUERY,服务器会用真实对端地址覆盖此字段,因此接受任意值 (不是纯 ip:port 的值会被替换为默认值 0.0.0.0:0) 。外部客户端应发送其自身的 ip:port
5 initial_time Int64 client INITIAL_QUERY_START_TIME (v54449) 查询开始时间 (微秒) 。固定宽度 8 字节,不是 VarUInt
6 query_interface UInt8 universal always 1 = TCP,2 = HTTP
7 os_user String client if interface = TCP 操作系统用户名
8 client_hostname String client if interface = TCP 客户端机器的 hostname
9 client_name String client if interface = TCP 客户端应用程序名称
10 version_major VarUInt 通用 如果接口 = TCP 客户端主版本号
11 version_minor VarUInt universal if interface = TCP 客户端次版本号
12 protocol_version VarUInt universal if interface = TCP 发起端客户端自身的 TCP 协议版本 (DBMS_TCP_PROTOCOL_VERSION) ,不是协商后的版本。对端 revision 仅决定有哪些字段存在;该值是 initiator 在编译时内置的版本,因此当较新的客户端与较旧的 server 通信时,它可能高于协商后的版本或 server revision。
13 quota_key String universal QUOTA_KEY_IN_CLIENT_INFO (v54060) 用于服务器端键控配额的资源配额键。不使用键控配额的客户端会发送空字符串。
14 distributed_depth VarUInt inter-server DISTRIBUTED_DEPTH (v54448) Distributed 查询的嵌套深度。外部客户端发送 0
15 version_patch VarUInt universal VERSION_PATCH (v54401), TCP only 客户端补丁版本号
16 open_telemetry (below) client OPEN_TELEMETRY (v54442) trace context。未启用 tracing 的客户端发送 0
17 collaborate_with_initiator VarUInt inter-server PARALLEL_REPLICAS (v54453) 以 VarUInt 表示的 Bool。外部客户端发送 0
18 count_participating_replicas VarUInt inter-server PARALLEL_REPLICAS (v54453) 外部客户端发送 0
19 number_of_current_replica VarUInt inter-server PARALLEL_REPLICAS (v54453) 外部客户端发送 0
20 script_query_number VarUInt client QUERY_AND_LINE_NUMBERS (v54475) 多条 statement 脚本中从 1 开始计数的 statement 位置。外部客户端发送 0
21 script_line_number VarUInt client QUERY_AND_LINE_NUMBERS (v54475) 源脚本中从 1 开始计数的行号。外部客户端发送 0
22 jwt_present UInt8 inter-server JWT_IN_INTERSERVER (v54476) 0 = 无 JWT;1 = 后续跟随 JWT。未使用 JWT 认证的外部客户端发送 0
23 jwt String inter-server JWT_IN_INTERSERVER (v54476), if jwt_present=1 JWT Bearer 令牌,仅在字段 22 = 1 时存在。
24 client_agent String client CLIENT_AGENT_IN_CLIENT_INFO (v54485) 尾部字段。客户端工具/agent 的标识符,会从环境中自动检测 (例如 claude-codecursorgemini-cli,或 AGENT 环境变量) 。外部客户端如果未检测到 agent,则发送空字符串。一旦协商版本 ≥ 54485,它就会出现在常规 Query 路径中 (在所有 interface 上发送,而不仅限于 TCP) 。
25 is_internal UInt8 client INTERNAL_QUERY_FLAG (v54486) 尾部字段。1 表示服务器内部查询 (不是由用户发起) ,会传播到远程查询,以便在 system.query_log 中将其标记为内部查询;与 query_kind (字段 1) 无关。外部客户端发送 0。一旦协商版本 ≥ 54486,它就会出现 (在所有 interface 上发送,而不仅限于 TCP) 。
26 当前_角色 UInt8 [+ String 列表] 服务器间 INTERSERVER_CURRENT_ROLES (v54488) 尾部字段。[UInt8 present],当 present = 1 时,后接一个包含发起方活动 (已启用) 角色名称的 [VarUInt count][String]*count 列表。允许次级节点根据发起方的角色而非用户的默认角色应用行策略。仅在 inter-server 查询且 push_external_roles_in_interserver_queries = 1 时填充;否则以及对于外部客户端,present = 0。一旦协商版本 ≥ 54488,就必须包含此存在标志字节 (在所有 interface 上发送,而不仅限于 TCP) 。对于 inter-server 查询,序列化后的列表还会并入 auth_hash (参见 Inter-server 身份验证) 。

OpenTelemetry 编码 (字段 16) :

[UInt8: has_trace]              0 = no trace data follows, 1 = trace data follows
If has_trace == 1:
  [16 bytes: trace_id]          byte-swapped per-8-bytes
  [8 bytes:  span_id]           byte-swapped
  [String:   trace_state]       W3C trace state
  [UInt8:    trace_flags]       W3C trace flags

服务器间身份验证

Query 的第 4 个字段 (auth_hash) 不是在线路上传输的共享集群 secret。发送原始密钥不仅会导致身份验证失败,还会泄露密钥。相反,作为服务器间客户端的服务端会使用加盐的 SHA-256 哈希来证明自己知道该密钥:

  1. 进入服务器间模式。 发起连接的服务端会在 ClientHello 中表明这一点:user 字段是服务器间标记,password 为空。然后,它会在同一个 ClientHello 数据包中,紧接 user/password 字段之后再附加两个字符串——cluster 名称,以及一个新生成的 32 字节 salt (随机值的 encodeSHA256) 。服务端会在发送 ServerHello 之前读取这两个字符串,因此客户端必须预先写入它们;如果先等待 ServerHello,就会发生死锁,因为服务端会阻塞并等待读取这两个字符串。
  2. 获取 nonce。 当协商了 INTERSERVER_SECRET_V2 (v54462) 时,ServerHello 会携带一个 8 字节的 UInt64 nonce。
  3. 计算哈希。 对于每个非 InitialQuery 的 Query 数据包,客户端会将 encodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles + current_roles) 写入第 4 个字段——即一个 32 字节摘要。 (nonce 是其十进制字符串形式,仅在协商版本 ≥ v54462 时存在;external_roles 仅在协商了 INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472) 时附加;current_roles 是序列化的角色名称列表——[VarUInt count][String]*count,不含存在字节——仅在协商了 INTERSERVER_CURRENT_ROLES (v54488) 且该列表存在时附加。) 对于 InitialQuery,或者在未配置集群 secret时,客户端则会写入空字符串。
  4. 验证。 服务端会以 32 字节上限读取第 4 个字段,并使用自己持有的集群 secret副本重新计算相同的拼接内容;如果摘要不同,则会拒绝该连接。

外部 (非服务器间) 客户端永远不会进入此模式,并且始终发送空的 auth_hash

设置

以内联方式编码在 Query body 的 settings 列表中 (Query 数据包的第 3 个字段) 。无论协商出的版本是什么,该列表都始终存在,并以一个 key 为空的 Setting 结尾——即单个 VarUInt 0,后面不再跟任何 flags 或 value。只有单个 setting 的编码方式取决于协商版本,并受 SETTINGS_SERIALIZED_AS_STRINGS (v54429) 控制。

v54429+ (STRINGS_WITH_FLAGS) — 每个 setting 都是如下所示的三元组:

# Field Type Role Description
1 key String 通用 Setting 名称。为空表示列表结束。
2 flags VarUInt 通用 元数据位 flags;见下文。
3 value String 通用 以字符串形式表示的 setting 值

key 为空时,字段 2 和 3 不存在。

Pre-54429 (BINARY) — 每个 setting 的编码形式为 [String key][特定类型的二进制值]不会写入 flags 字段,value 也会按该 setting 的原生二进制形式编码 (例如定宽整数或带长度前缀的字符串) ,而不是编码为十进制/文本字符串。该列表仍以空 key 结尾。以低于 54429 的协商版本为目标的 client,必须读写这种二进制形式,而不是上面的三元组。 (自定义 setting 属于例外:无论哪种编码,它们始终都带有 flags 和字符串 value。)

flags 字段包含:

  • 0x01Important:该 setting 会影响查询结果,旧版 peer 不得静默忽略它。
  • 0x02Custom:用户定义的自定义 setting。
  • 0x0c — 一个 2 位层级 字段,而非独立 flag:0x00 = Production,0x04 = Obsolete,0x08 = Experimental,0x0c = Beta。应读取完整 2 位 (flags & 0x0c) ——如果简单测试 flags & 0x04,会把 Beta (0x0c) 误判为 Obsolete。
  • 0x80HotReload (无需重启即可重载 config;在 flags 枚举中定义,主要见于协调 settings) 。

参数

查询参数,用于参数化查询,例如 SELECT {x:UInt64}。其编码方式与设置了 Custom 标志 (0x02) 的 Setting 完全相同,并同样以空 key 作为结束标记。

# 字段 类型 角色 描述
1 key String 客户端 参数名称。空值 = 列表结束。
2 flags VarUInt 客户端 始终为 0x02 (Custom)
3 value String 客户端 以字符串形式表示的参数值。请参见下文关于引号的说明。

Data (数据包类型 1 server→client,数据包类型 2 client→server)

两个方向均使用此类型。它承载结果块、INSERT 数据、外部表以及数据结束标记。

传输格式是对称的——两个方向都会在块前包含一个 table_name 前缀。只有数据包类型字节不同。

[VarUInt: packet_type]     1 (server→client) or 2 (client→server)
[String:  table_name]      External table name; empty in most cases
[Block]                    See the Native Format spec for the Block layout
字段 类型 作用 描述
table_name String 通用 外部表名称。空值 ("") 是最常见的情况——用于主表、查询结果以及 INSERT 行流。仅 table_name 为空 并不表示数据结束 (普通的 INSERT 行数据包也会携带 "") 。
Block body 请参见 块和列结构

数据结束标记是指 Block 为空的数据包——即 0 列和 0 行——与 table_name 的值无关。只有当解码后的块为空 (block.empty()) 时,服务器才会将客户端的 Data 数据包视为终止符;带有 table_name = "" 且块非空的数据包只是普通的行数据包,并非终止符。因此,INSERT 行流由一系列非空 Data 块组成,最后以一个空的 Data 块结束。

有关块的各种变体及其含义,请参见 块变体

Progress (数据包类型 3)

服务器 → 客户端。在查询执行期间定期发送。所有字段均为 VarUInt,并且每个数据包携带的都是自上一个 Progress 数据包以来的增量,而非累计总数。发送前,服务器会读取其计数器,并以原子方式将其重置为零,同时将 elapsed_ns 计算为自上次发送以来的时间差。因此,客户端必须在本地累加后续收到的数据包,才能得到持续更新的总数——如果把某个数据包当作绝对值,一旦收到多个数据包,进度显示就会回跳或少计。

# 字段 类型 作用 条件 描述
1 rows VarUInt 通用 始终 自上一个数据包以来读取的行数 (加到累计总数中)
2 bytes VarUInt 通用 始终 自上一个数据包以来读取的字节数 (加到累计总数中)
3 total_rows VarUInt 通用 始终 预计待读取总行数的增量;需要累加 (在某个数据包中可能为 0)
4 total_bytes VarUInt 通用 TOTAL_BYTES_IN_PROGRESS (v54463) 预计待读取总字节数的增量;需要累加。在线路格式中位于 total_rowswrote_rows 之间。
5 wrote_rows VarUInt 通用 WRITE_CLIENT_INFO (v54420) 自上一个数据包以来写入的行数 (用于 INSERT) ;需要累加
6 wrote_bytes VarUInt 通用 WRITE_CLIENT_INFO (v54420) 自上一个数据包以来写入的字节数 (用于 INSERT) ;需要累加
7 elapsed_ns VarUInt 通用 SERVER_QUERY_TIME_IN_PROGRESS (v54460) 自上一个数据包以来经过的纳秒数 (是增量,不是查询总耗时) ;需要累加

ProfileInfo (数据包类型 6)

服务器 → 客户端。每个查询发送一次,通常在执行接近结束时发送。

# Field Type Role Condition Description
1 rows VarUInt 通用 始终 已处理的总行数
2 blocks VarUInt 通用 始终 已处理的总块数
3 bytes VarUInt 通用 始终 已处理的总字节数
4 applied_limit Bool 通用 始终 是否应用了 LIMIT 子句
5 rows_before_limit VarUInt 通用 始终 LIMIT 之前的行数
6 obsolete Bool 通用 始终 已废弃的兼容性字节。服务器始终在此处写入 true,而客户端在读取时会将其丢弃;它不是“已计算 rows_before_limit”标志。真正有意义的 limit 状态由字段 4 (applied_limit) 和字段 5 共同表示。读取后忽略即可。
7 applied_aggregation Bool 通用 ROWS_BEFORE_AGGREGATION (v54469) 是否应用了 GROUP BY
8 rows_before_aggregation VarUInt 通用 ROWS_BEFORE_AGGREGATION (v54469) 聚合之前的行数

Totals (数据包类型 7)

服务器 → 客户端。对于包含 WITH TOTALS 的查询,会发送此数据包。其传输格式与 Data 完全一致:一个 table_name 字符串 (始终为空) ,后面跟着一个块。不同之处仅在于数据包类型字节。

[VarUInt: 7]                packet type
[String:  table_name]       always empty
[Block]                     see the Native Format spec

极值 (数据包类型 8)

服务器 → 客户端。在启用 extremes 设置时发送。传输格式与 Data 完全相同。该块恰好包含 2 行:第 0 行保存每一列的最小值,第 1 行保存每一列的最大值。

[VarUInt: 8]                packet type
[String:  table_name]       always empty
[Block]                     num_rows = 2

日志 (数据包类型 10)

服务器 → 客户端。当查询存在活动的日志队列时,会发送此数据包 (由 send_logs_level 设置控制;参见日志流式传输) 。

其封装和消息体格式与 Data 相同。该块的 num_columns = 8 为固定值,并具有预定义的 schema。每条日志记录对应一行,分布在全部 8 列中;单个日志数据包可携带多行。

[VarUInt: 10]               packet type
[String:  table_name]       always empty
[Block]                     num_columns = 8, num_rows = number of log lines

这 8 列的顺序必须严格如下:

# Name Type Description
1 event_time 日期时间 事件时间戳 (自纪元以来的秒数)
2 event_time_microseconds UInt32 微秒部分
3 host_name String 产生日志的服务器主机名
4 query_id String 该日志所属的查询 ID
5 thread_id UInt64 操作系统线程 ID
6 priority Int8 日志级别 (Poco 优先级:1 = Fatal,… 8 = Trace,9 = Test)
7 source String 日志记录器名称
8 text String 日志消息内容

ProfileEvents (数据包类型 14)

Server → Client。携带每个查询的性能计数器。

其封套和 body 格式与 Data 相同。该块的 num_columns = 6 为固定值,并具有预定义的 schema。每个事件对应一行。

[VarUInt: 14]               packet type
[String:  table_name]       always empty
[Block]                     num_columns = 6, num_rows = number of events

这 6 列:

# 名称 类型 描述
1 host_name String 服务器主机名
2 current_time DateTime 事件时间戳
3 thread_id UInt64 线程 ID
4 type Enum8 事件类型:1 = Increment (计数器) ,2 = Gauge。底层存储使用一个有符号字节。
5 name String 事件名称 (例如:"Query""NetworkReceiveBytes")
6 value Int64 计数器值或 Gauge 值

TableColumns (数据包类型 11)

Server → Client,由 COLUMN_DEFAULTS_METADATA (v54410) 控制。server 会在 INSERT schema 块之前发送该数据包,用于携带列默认值元数据,但仅当协商版本 ≥ 54410 启用了 input_format_defaults_for_omitted_fields setting 时才会发送。低于 54410 时,该数据包绝不会发送,因此较旧的 client 不得 等待它——schema Data 块会直接到来。v54410+ 的 client 应准备好处理任意一种顺序:先收到可选的 TableColumns,然后是 schema 块。

# Field Type Role Description
1 external_table String universal 外部表名称。空值 = 主表。
2 columns_description String universal 文本形式的列定义,例如 "id Int32, name String DEFAULT ''"。这是自由格式文本——应按字符串 parse。

TimezoneUpdate (数据包类型 17)

Server → Client,由 TIMEZONE_UPDATES (v54464) 控制。它只在一个地方发送:input 表函数的初始化器中 (即形如 INSERT INTO <table> SELECT ... FROM input('<structure>') 的查询,会从客户端流式传输行) 。服务器发送输入 schema 的 Data 块后 (见INSERT 阶段) ,会立即发出 TimezoneUpdate,携带查询上下文当前的 session_timezone,以便客户端用相同的时区解析接下来要发送的行。对于查询执行过程中任意的 SET session_timezone 变更,服务器不会发送此数据包;它也不会用这个数据包告诉客户端如何格式化后续返回的结果块。

# 字段 类型 作用 描述
1 timezone String 通用 新的会话默认时区 (例如 "UTC""Europe/Berlin") 。

该数据包只会到达一次:紧接在输入 schema 块之后,且在客户端开始发送行块之前。即使解码器忽略 TimezoneUpdate,也必须继续读取后面的 String,以保持线协议对齐。

SSH 质询-响应身份验证 (packet types 11, 12, 18)

SSH_AUTHENTICATION (v54466) 控制,且默认不启用,需显式选择启用。当 ClientHello 发送 user = " SSH KEY AUTHENTICATION " + <real_user> (包含前导和尾随空格) 以及 password = "" 时,连接会进入 SSH 流程。服务器会读取此前缀,将其剥离以还原真实用户名,然后切换到质询-响应模式。

Packet Code Direction Body
SSHChallengeRequest 11 Client → Server (无 body)
SSHChallenge 18 Server → Client String challenge — 随机字节;构成待签名字符串的一个组成部分 (见下文)
SSHChallengeResponse 12 Client → Server String signature — 对下文定义的拼接结果进行的 SSH 签名,不是对原始 challenge 的签名

此流程会替代密码身份验证,并且质询-响应交换发生在 ServerHello 之前——服务器会推迟发送 Hello 回复,直到身份验证成功:

  1. 客户端发送带有 SSH 标记前缀且密码为空的 ClientHello。

  2. 客户端发送 SSHChallengeRequest (packet 11) 。此时服务器 尚未发送 ServerHello——它会先处理身份验证,并在此阻塞等待该 packet。

  3. 服务器回复 SSHChallenge,携带随机字节 (packet 18) 。

  4. 客户端构建待签名字符串,并对该字符串进行签名,而不是对原始 challenge 进行签名,然后发送携带签名的 SSHChallengeResponse (packet 12) 。签名消息是以下四个部分按字节拼接的结果,不带任何分隔符,并且严格按以下顺序排列:

    to_sign = decimal(protocol_version) + default_database + user + challenge
    Part Source
    decimal(protocol_version) 客户端的协议版本,以十进制 ASCII 字符串表示 (例如 "54466") ——版本号是字符串形式,而不是 VarUInt 或定宽整数。服务器会使用其在 ClientHello 中收到的同一协议版本进行校验。
    default_database ClientHello 中的 database field (如果没有则为空字符串) 。
    user 剥离 " SSH KEY AUTHENTICATION " 标记前缀后的真实用户名——也就是服务器在去掉此前缀后还原出的同一个名称。
    challenge 来自 SSHChallenge packet 的原始 challenge 字节。
  5. 服务器使用该用户已注册的 public key 验证签名,并重建相同的 decimal(protocol_version) + default_database + user + challenge 字符串。成功后,它会发送 ServerHello——与密码流程中的回复相同——随后 handshake 将正常继续 (Addendum 等) ;失败时,它会返回 Exception 并终止连接。仅对原始 challenge 字节签名的客户端将无法通过身份验证。

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: ClientHello (SSH marker user, empty password)
    C->>S: SSHChallengeRequest (11)
    Note over C,S: Server has NOT sent ServerHello yet —<br/>it authenticates first and blocks here
    S->>C: SSHChallenge (18) — random bytes to sign
    C->>S: SSHChallengeResponse (12) — signature over version+db+user+challenge
    alt signature verifies
        S->>C: ServerHello — handshake continues normally
    else verification fails
        S->>C: Exception — connection terminates
    end

不使用 SSH 认证的外部客户端永远不会看到数据包 11、12 或 18——除非用户通过用户名此前缀显式选择启用,否则这些数据包不会在线路上传输。

MergeTreeAllRangesAnnouncementResponse (数据包类型 14)

客户端 → 服务器,仅用于服务器间通信。仅在 parallel_replicas_protocol_version ≥ 8 时可用 (参见 VERSIONED_PARALLEL_REPLICAS_PROTOCOL) 。外部客户端绝不会发送此数据包。

当协商出的 parallel-replicas 版本为 ≥ 8 时,发起端针对跟随者的 MergeTreeAllRangesAnnouncement (数据包类型 15,server→client 方向) 的请求/响应流程会发生变化:

  1. 跟随者打开其读取管道,并向发起端发送 MergeTreeAllRangesAnnouncement
  2. 仅当通知中的 mode 不是 Default (WithOrder = 1ReverseOrder = 2,两者都用于按序并行读取) 时,发起端才会回复 MergeTreeAllRangesAnnouncementResponse。对于 mode = Default = 0,发起端会保持静默,而跟随者也不会等待——Default 模式会在每个 MergeTreeReadTaskRequest 中分发 range,且永远不需要预先提供的分片列表。
  3. 跟随者会在发出第一个 MergeTreeReadTaskRequest (服务器数据包 16——由 follower→initiator 发送;发起端则回复 MergeTreeReadTaskResponse,客户端数据包 10) 之前阻塞等待响应 (如果预期会有响应) ,并使用返回的分片列表,将 source 的构建范围过滤到恰好由其 #split_i stream 拥有的分片。

在版本 8 以下,无论 mode 如何,该通知都是发出即忘,而跟随者会基于本地已知的每个分片构建 source (这是 legacy behaviour) 。

正文

# 字段 类型 描述
1 version Int64 (小端序) 发送方的并行副本协议版本。当接收方的 TCP 修订版本 ≥ DBMS_MIN_REVISION_WITH_VERSIONED_PARALLEL_REPLICAS_PROTOCOL (54471) 时,其值等于 DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION (当前为 8) ;否则回退为 DBMS_MIN_SUPPORTED_PARALLEL_REPLICAS_PROTOCOL_VERSION (3) 。接收方会拒绝任何低于 DBMS_MIN_SUPPORTED_PARALLEL_REPLICAS_PROTOCOL_VERSION 的值。
2 parts RangesInDataPartsDescription 协调器为该通知对应的 stream 已登记、并以其记录为准的 parts 集合。空列表表示该 stream 在协调器上不存在 (例如,follower 通知的 split 数量多于 initiator 实际创建的数量) ;该 stream 对应的 follower pool 会立即将自身标记为已完成。
3 stream_id String 回显此响应所对应通知的 stream_id (在使用 split 拓扑时,为表名加上 #split_i 后缀) 。

RangesInDataPartsDescription 消息体

# 字段 类型 描述
1 count VarUInt 后续 part 描述符的数量。解码器会将大于 100'000'000'000 的值视为格式错误并拒绝。
2 parts RangesInDataPartDescription 重复 count 这些描述符,按协调器的注册顺序排列。

RangesInDataPartDescription 消息体

# 字段 类型 Gate 描述
1 info MergeTreePartInfo universal Part 标识 (分区、块范围、层级、变更) 。
2 ranges MarkRanges universal 该流可提供的 info 中的标记范围。空列表表示该分片已注册,但当前未分配任何工作。
3 rows VarUInt universal ranges 覆盖的总行数。
4 projection_name String DBMS_PARALLEL_REPLICAS_MIN_VERSION_WITH_PROJECTION (PR v5) 对于主分片的行,该值为空;否则为投影名称。
5 min_marks_per_task VarUInt DBMS_PARALLEL_REPLICAS_MIN_VERSION_WITH_MIN_MARKS_PER_TASK (PR v6) follower 节点的池为此分片将标记批量合并为单个读取任务时,每个任务的最少标记数。

MergeTreePartInfo 正文

# Field Type Description
1 version Int64 (little-endian) 始终为 DBMS_MERGE_TREE_PART_INFO_VERSION (1) 。解码器会拒绝任何其他值。
2 partition_id String 分区标识符 (例如,未分区表为 "all",或分区键 Tuple 表达式转成字符串后的值) 。
3 min_block Int64 (little-endian) 该分片的块范围中的第一个块编号。
4 max_block Int64 (little-endian) 该分片的块范围中的最后一个块编号 (含该值) 。
5 level UInt32 (little-endian) 合并级别。
6 mutation Int64 (little-endian) 生成该分片的变更版本 (未发生变更时为 0) 。
7 use_legacy_max_level Bool (text) 编码为单个 ASCII 字节 ('1''0') ——用于分片名称格式的历史兼容标志。

MarkRanges 消息体

# 字段 类型 描述
1 size UInt64 (小端序) 后续标记范围对的数量。注意:采用小端序定宽编码,不是 VarUInt。
2 ranges size 次重复的 (UInt64 begin, UInt64 end),每个值均为小端序 半开区间 [begin, end) 标记区间。

数据包类型参考

客户端 → 服务器

代码 名称 消息体格式 描述
0 Hello ClientHello 握手发起
1 Query Query 查询执行请求
2 Data Data 数据块 (INSERT 数据、外部表、数据结束标记)
3 Cancel (无消息体) 取消正在运行的查询
4 Ping Ping 存活性检查
5 TablesStatusRequest 未指定 表状态检查
6 KeepAlive 未指定 连接保活
7 Scalar 未指定 标量数据块
8 IgnoredPartUUIDs 未指定 查询中要排除的分片
9 ReadTaskResponse 未指定 S3 集群读取响应
10 MergeTreeReadTaskResponse 未指定 并行读取任务响应
11 SSHChallengeRequest SSH 身份验证 SSH 身份验证质询请求
12 SSHChallengeResponse SSH 身份验证 SSH 身份验证质询响应
13 QueryPlan 未指定 查询计划
14 MergeTreeAllRangesAnnouncementResponse MergeTreeAllRangesAnnouncementResponse initiator 对 follower 的 MergeTreeAllRangesAnnouncement 的回复 (受 parallel_replicas_protocol_version ≥ 8 限制——参见 VERSIONED_PARALLEL_REPLICAS_PROTOCOL) 。仅用于服务器间通信——外部客户端绝不会发送。

服务器 → 客户端

代码 名称 消息体格式 描述
0 Hello ServerHello 握手响应
1 Data Data 结果数据块
2 Exception Exception 错误
3 Progress Progress 查询执行进度
4 Pong Pong 存活检查响应
5 EndOfStream (无消息体) 查询完成
6 ProfileInfo ProfileInfo 执行后的 profiling 数据
7 Totals Totals GROUP BY WITH TOTALS 行
8 极值 极值 最小/最大值 (2 行数据块)
9 TablesStatusResponse 未指定 表状态响应
10 日志 日志 查询执行日志行
11 TableColumns TableColumns 默认值的列描述
12 PartUUIDs 未指定 唯一 part ID
13 ReadTaskRequest 未指定 集群读取任务请求
14 ProfileEvents ProfileEvents 性能计数器
15 MergeTreeAllRangesAnnouncement 未指定 并行读取初始化
16 MergeTreeReadTaskRequest 未指定 并行读取任务分配
17 TimezoneUpdate TimezoneUpdate 服务器时区更新
18 SSHChallenge SSH auth SSH 身份验证质询

配置

本节介绍会影响原生协议连接形态的可调参数:

下方的默认值反映的是较新的服务器版本;它们可能因版本和部署而异。

传输层设置

套接字选项

选项 默认值 说明
TCP_NODELAY 开启 两端 已禁用 Nagle 算法。小数据包会立即发送。
SO_KEEPALIVE 开启 (客户端) ,操作系统默认值 (服务端) 非对称 内核级 TCP keepalive 探测。当 tcp_keep_alive_timeout > 0 时,客户端会显式启用此项。服务端继承操作系统默认值。
SO_RCVBUF / SO_SNDBUF 操作系统默认值 套接字缓冲区大小。协议不会对其进行调优。

超时

Setting Default Unit Side Description
connect_timeout 10 客户端 建立初始 TCP 连接的超时时间。
handshake_timeout_ms 10000 毫秒 客户端 握手期间接收 ServerHello 的超时时间。
send_timeout 300 双方 如果在此时间间隔内无法写入任何字节,连接将抛出异常。
receive_timeout 300 双方 如果在此时间间隔内无法读取任何字节,连接将抛出异常。
tcp_keep_alive_timeout 290 客户端 在 OS 发送第一个 TCP keepalive 探测包前的空闲时长。
receive_data_timeout_ms 2000 毫秒 客户端 从副本接收第一个 Data 包的超时时间。
connect_timeout_with_failover_ms 1000 毫秒 客户端 遍历各副本时,每次连接尝试的超时时间。
connect_timeout_with_failover_secure_ms 1000 毫秒 客户端 通过 TLS 遍历各副本时,每次连接尝试的超时时间。
hedged_connection_timeout_ms 50 毫秒 客户端 对冲请求中每次连接尝试的超时时间。
poll_interval 10 服务端 服务端空闲连接和关闭检查循环的粒度。

这些超时的嵌套关系如下:

tcp_keep_alive_timeout (290s)
      < receive_timeout (300s)
      < idle_connection_timeout (3600s)
      < tcp_close_connection_after_queries_seconds (0 = unlimited by default)

操作系统的 keepalive 会最先生效,并且可能在内核层静默检测到失效的对端。应用程序的接收超时是下一道防线。空闲超时则是最后一道手段,用于回收长时间未使用的连接。

连接限制

Setting Default Unit Side Description
max_connections 4096 计数 服务器 最大并发 TCP 连接数。
idle_connection_timeout 3600 服务器 空闲连接可保持打开状态的最长时间。
tcp_close_connection_after_queries_num 0 (无限制) 计数 服务器 连接在被强制关闭前允许执行的最大查询数。
tcp_close_connection_after_queries_seconds 0 (无限制) 服务器 无论是否有活动,连接总生命周期的最长时长。

只要连接持续定期发出查询,就可以无限期保持存活。只有空闲连接会在一小时后被回收,且默认不设最大生命周期限制。

应用层设置

这些设置会随每个查询一起,通过Query 数据包的 settings 列表传递。它们会改变服务器在线上传输的数据内容,或其分帧方式。

压缩

设置 默认值 单位 描述
network_compression_method "LZ4" String 当 Query 数据包的 compression 标志位被设置时,使用的压缩编解码器。取值:"LZ4""LZ4HC""ZSTD""NONE"
network_zstd_compression_level 1 1–15 network_compression_method == "ZSTD" 时的 ZSTD 级别。

Query 数据包 (字段 6) 中的 compression 标志位用于开启或关闭压缩;这些设置用于选择开启压缩时使用的编解码器。

日志流式传输

Setting Default Unit Description
send_logs_level "fatal" string 最低日志级别。取值:"none""fatal""error""warning""information""debug""trace""test"
send_logs_source_regexp "" string 对日志记录器来源应用的 Regex 过滤器。为空表示所有来源都会通过。

send_logs_level 设为除 "none" 之外的任意值时,服务器会在查询执行期间发送 日志 数据包。

进度报告

设置 默认值 单位 描述
interactive_delay 100000 微秒 连续两个 Progress 数据包之间的目标最小间隔。

这是目标最小值,而非严格的最大值:如果查询生成工作的速度不够快,服务器发送 Progress 数据包的频率可能会更低。

结果封装

设置 默认值 单位 说明
extremes false bool 为 true 时,服务器会发送一个 极值 数据包,其中包含每列的最小值/最大值。
max_result_rows 0 (无限制) count 传输行数上限。其行为由 result_overflow_mode 控制。
max_result_bytes 0 (无限制) uncompressed bytes 未压缩字节量上限。其行为由 result_overflow_mode 控制。
result_overflow_mode "throw" string "throw" 会以 Exception 结束该流;"break" 会发送部分结果,随后发送 EndOfStream。

异步 INSERT

设置 默认值 单位 描述
async_insert true bool 为 true 时,INSERT 数据会在服务器端排队并进行批处理。
wait_for_async_insert true bool 为 true 时 (且启用 async_insert) ,服务器会暂不返回响应,直到队列中的数据刷写完成。
wait_for_async_insert_timeout 120 服务器在返回前等待刷写完成的最长时间。

分布式链路追踪

设置 默认值 单位 描述
opentelemetry_start_trace_probability 0.0 0–1 概率 在服务器端将 OpenTelemetry 上下文附加到响应遥测数据的概率。

不在此范围内的设置

这些设置有时会被误认为是协议级设置,但它们控制的是 SQL 执行、存储或 CPU 使用,而不是线上传输行为。协议实现不需要对它们做特殊处理。

  • max_threads — 查询执行期间的并行度。
  • max_memory_usage — 单个查询的内存上限。
  • max_block_size, preferred_block_size_bytes — 查询处理期间的内部块大小;线上传输的块不受这些设置影响。
  • compile_expressions — JIT 编译;仅影响 CPU。
  • async_insert_max_data_size — 服务端队列缓冲区。
  • 所有 input_format_*output_format_* 设置,除了 input_format_native_* / output_format_native_* 家族 —— 非 native 设置用于选择或调整其他格式 (例如通过 HTTP) ,不会改变原生协议的 Data 块。

*_native_* 设置是个例外:它们会改变原生 TCP Data 块中的字节,因此协议实现必须将其考虑在内。output_format_native_encode_types_in_binary_format 会将列的 type 字段从文本字符串切换为二进制类型编码,output_format_native_write_json_as_string 会将 JSON 列输出为 String,而 output_format_native_use_flattened_dynamic_and_json_serialization 则会选择 FLATTENED Dynamic/JSON 布局。由于这些设置影响的是块体而非数据包封装,因此它们在 Native Format 规范中定义——请参见列在线路上的布局带版本的类型

术语表

Cancel — 由客户端发起的数据包 (类型 3) ,用于中止正在运行的查询。本页未对此作详细说明。

客户端数据结束标记 — 客户端发送的空 Data 数据包 (0 列、0 行) ,用于关闭输入流。其所在位置因查询类型而异:

  • 普通查询 (SELECT 等) : 在 Query 数据包以及所有外部表 Data 数据包之后发送,用于表示“没有更多外部数据”。随后服务器开始执行。
  • INSERT 客户端不会发送 schema 之前的标记。服务器会先发送 schema 块,客户端再流式传输其行 Data 块,最后才发送空 Data 数据包来终止行流。如果在 schema 块之前发送空标记,服务器会将其视为行已立即结束,从而导致数据丢失。

特性 — 在特定协议版本中引入的一项线格式变更。当协商后的版本大于或等于该特性对应的版本时生效。参见版本控制与特性门控

Inter-server — 某个字段的角色标签,仅在服务器到服务器的分布式查询中有意义。外部客户端会写入默认值 (通常为空字符串、0 或 false) 。

协商版本min(client_version, server_version),在握手期间计算得出。它决定了在连接的整个生命周期内哪些特性处于激活状态。

数据包 — 一条线上传输消息:以 VarUInt 数据包类型代码开头,后接一个 body,其格式取决于类型。参见数据包封装

数据包类型代码 — 数据包开头的 VarUInt,用于标识其格式。目前 0–18 的值已分配。参见数据包类型参考

响应流 — 服务器在查询期间发出的数据包序列。其长度不定,并且恰好以一个 EndOfStream (成功) 或 Exception (失败) 结束。参见查询阶段

Schema 块 — INSERT 阶段中服务器发送的头部块 (即一个有列但 0 行的 Block) ,用于在客户端发送数据前声明预期的列形态。

Settings 列表 — Query body 中由 (key, flags, value) 元组组成的序列,以空 key 终止。它承载每个查询的应用层配置。参见 Setting

StageQuery 数据包中的一个 VarUInt 字段 (字段 5) ,用于控制服务器将查询执行到什么程度。外部客户端通常发送 2 (Complete) ;分布式查询和序列化查询计划会使用更高的值。完整的线传输取值请参见 Query 的字段 5。

终止符 — 用于结束流的数据包。Query 响应以 EndOfStream (成功) 或 Exception (失败) 结束。客户端的输入流则以空 Data 标记结束。

Navigation