Skip to main content

Experimental 状态

原生 TCP 客户端 API 仍处于实验阶段,其对外接口可能会在后续版本中发生变化。
主客户端、数据源、会话和操作接口,以及依赖注入扩展 都会触发编译器警告 CHTCP0001。评估该 API 时,若已知悉此警告,可将其 添加到项目文件的 NoWarn 中:
只有在确认并接受使用该 API 的代码可能需要在后续版本中修改后,才应屏蔽此警告。

支持的 .NET 版本

原生 TCP 客户端支持以下 .NET 版本:
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

支持的 ClickHouse 版本

TCP 客户端官方支持 ClickHouse 服务器最近的 2 个长期支持版和最近的 3 个发行版。

安装

原生 TCP 客户端包含在 ClickHouse.Driver 包中。请通过 NuGet 安装:
也可以使用 NuGet 包管理器:

快速入门

从 HTTP 客户端迁移

TCP API 与 IClickHouseClient 相互独立,并非 ClickHouseClient 或 ClickHouseConnection 的传输选项。大部分 SQL 可直接复用,但需要创建 ClickHouseTcpClient,并将每项操作改为使用对应的 TCP 实现。

方法对照

例如,HTTP 客户端中的读取器循环:
变为异步 TCP 行数据流:

选项和参数

HTTP 的 QueryOptions 和 ClickHouseParameterCollection 不能与对应的 TCP 类型互换。 请将相关值移至 ClickHouseTcpQueryOptions:设置以字符串形式表示, 参数则包含在选项对象中:
TCP 客户端支持 ClickHouse 原生的 {name:Type} 占位符,但不会改写 ADO.NET 风格的 @name 占位符。它也不支持按查询指定数据库、角色、Bearer 令牌、自定义 请求头、参数解析器或格式说明符,也不支持读取转换器。有关仍仅受 HTTP 支持的 功能,请参阅限制。

连接与结果的生命周期

这两种高级客户端都是线程安全的,应重复使用。TCP 客户端持有持久的原生 连接,而非 HTTP 连接;它实现了 IAsyncDisposable,通常应在应用程序关闭时 异步释放。 QueryAsync 返回的行由调用方管理。StreamAsync 返回的块及其列是借用数据,仅在 当前迭代期间有效;如果需要在迭代结束后继续使用其中的值,必须将其复制。务必完成枚举或释放 枚举器,以便将连接归还连接池。如果多个操作必须在同一连接上执行并保留会话状态, 请使用 OpenSessionAsync。 如需查看在同一张表上使用这两种客户端的完整示例,请参阅 从 HTTP 迁移到 TCP 的示例。

配置

原生 TCP 客户端有两种配置方式:
  • **连接字符串:**由分号分隔的键值对组成,用于指定服务器端点、 身份验证凭证及其他连接选项。
  • **ClickHouseTcpClientOptions 对象:**在代码中设置的强类型、不可变配置对象。
将任一形式传入 ClickHouseTcpClient 构造函数。使用 ClickHouseTcpConnectionStringBuilder 解析或构建连接字符串,并将其转换为 ClickHouseTcpClientOptions。
以下各节列出了可用的配置项、对应的默认值及其作用。

连接设置

连接字符串中,TimeSpan 属性的值以秒为单位。

数据格式与序列化

连接池

每个连接同一时间只能执行一项操作。连接池通过复用连接,让多项操作 并发执行。

安全

不使用 TLS 时,密码和所有查询数据都会以明文形式通过网络传输。在本地开发环境之外,请始终将 TlsAllowInvalidCertificates 设为 false。
安全的原生协议使用独立的服务器端点,而不是在现有连接中升级为 TLS。TLS 客户端必须连接到安全的原生协议端口,无法回退到明文连接。ConfigureTls 会在 最后应用,并且可以覆盖证书和主机名验证设置,因此请仔细检查自定义回调。

日志与调试

客户端日志记录器记录客户端的生命周期和操作事件。服务器日志数据包则发送 到 ClickHouseTcpQueryCallbacks.OnLog。语句文本可能包含敏感数据,因此默认情况下 不会纳入活动标签,在调试日志中也会被大幅截断。

自定义设置

ClickHouseTcpClientOptions.CustomSettings 会将 ClickHouse 服务器设置应用于每次查询和 插入操作。如果设置同名,针对单次操作指定的 ClickHouseTcpQueryOptions.Settings 配置值会覆盖客户端级别的配置值。
在连接字符串中,为每项服务器设置加上 set_ 前缀:
向 CustomSettings 或每项操作的 Settings 添加设置时,不要带上 set_ 前缀。

ClickHouseTcpQueryOptions

ClickHouseTcpQueryOptions 用于配置单次操作的设置。所有属性均为可选项。

ClickHouseTcpInsertOptions

ClickHouseTcpInsertOptions 在 ClickHouseTcpQueryOptions 的基础上增加了原生块 插入的相关设置。所有查询选项属性也均可使用。

ClickHouseTcpClient

ClickHouseTcpClient 是原生 TCP 协议的高级 API。它可执行语句和 标量查询,以列式块、行或 POCO 的形式流式返回结果,插入列式数据或行数据, 并创建绑定到特定连接的会话。 该客户端支持多线程安全使用,并管理一个连接池。请为每个端点创建一个客户端,在整个 应用程序中共享,并在应用程序关闭时释放。操作可并发执行,并发数上限为 MaxPoolSize;超出上限的操作最多等待 PoolTimeout 以获取连接。

创建客户端

可以通过连接字符串或 ClickHouseTcpClientOptions 创建客户端。可用设置请参阅 配置。 使用连接字符串:
使用强类型选项:
建议优先采用异步方式释放客户端。释放客户端时会关闭空闲连接,并等待正在执行的操作完成,最长等待时间为 PoolTimeout;超时后仍未完成的操作将被中止。

依赖注入

AddClickHouseTcpDataSource 注册一个单例数据源,并以 IClickHouseTcpClient 和 IClickHouseTcpOperations 的形式提供其共享客户端:
服务提供程序持有数据源和连接池。不要释放通过依赖注入获得的客户端; 应在应用程序关闭时释放服务提供程序。如果选项中未指定 LoggerFactory, 注册时将使用容器中的 ILoggerFactory。使用 serviceKey 可以注册多个 相互独立的数据源。

执行查询

执行不返回行的语句 (如 DDL 语句和变更) 时,请使用 ExecuteAsync:
服务器确认该语句后,此方法便会完成。如果语句返回数据行, ExecuteAsync 会读取并丢弃这些行。对于 INSERT INTO ... VALUES,请使用 InsertAsync 或 InsertRowsAsync,并使 SQL 语句以 VALUES 结尾,不要在语句中直接写入值。

读取数据

根据应用程序所需的结果形式选择读取 API:

标量读取

ExecuteScalarAsync 返回第一行第一列的值,类型为 object:
该方法会读取完整响应,并丢弃第一个值之后的所有值。请编写只返回一行的查询, 不要试图用该方法提前停止读取大量结果。无论第一个值为 NULL,还是查询 没有返回任何行,结果都是 null。

逐行读取

QueryAsync 以流式方式返回每一行,每行都是独立的 object[],其元素按结果列的顺序排列:
枚举器移至下一项后,行数组仍然有效。如果没有结果 模型,使用这一层级很方便,但它会为每一行分配一个数组,并对值类型进行装箱。

读取 POCO 对象

QueryAsync<T> 将列映射到具有公共 setter 的属性。名称匹配不区分大小写,并忽略 下划线;可使用 ClickHouseTcpColumn 显式指定名称,使用 ClickHouseTcpNotMapped 排除属性。
POCO 映射会进行类型转换,并为每行分配一个对象。

按列式块读取

StreamAsync 以一系列 Block 值的形式提供原生列式查询结果。这是读取数据最快的方式。
块采用借用方式提供。块、其中的列及其值跨度仅在当前 迭代中有效。不要释放或留存它们;任何需要在循环体执行结束后继续使用的数据,都必须显式复制到由你管理的内存中。
务必完成枚举,或释放枚举器,以便将其连接归还连接池。
Block.Column<T> 访问解码器生成的 CLR 类型。需要进行类型转换时,请使用 Block.ReadAs<T> 或 IDateTimeColumn 等专用接口。

插入数据

原生插入以列式传输块的形式发送数据。SQL 必须是 INSERT INTO ... VALUES 语句,且不能包含内联值列表。使用类型化列可最大限度减少客户端的处理工作;如果应用程序中的数据已经按行组织,则使用行或 POCO 重载。

列式插入

InsertAsync 接受已按类型化列组织的数据。这是插入数据最快的方式。
各列按名称而非参数顺序与 SQL 列列表对应,且行数必须相同。对于语句中未指定的表列,服务器会填入默认值。插入完成前,请勿修改 调用方持有的数组。 此 API 无需进行按行插入所需的行列转换和值类型装箱。从借用的结果块中读取的列也可以直接传递给 InsertAsync,但必须确保 插入在该块释放前完成。

按行插入

InsertRowsAsync 接受 object[] 格式的行数据。各值按顺序与 SQL 列列表中的列对应:
行按块逐一转换为列。每行都必须包含语句中指定的每一列对应的一个值, 且同一列中的值必须采用一致的 CLR 类型,除非目标列的类型为 Variant 或 Dynamic。 操作完成前,请勿修改这些行。

POCO 插入

泛型 InsertRowsAsync<T> 重载遵循与 POCO 读取相同的命名和特性规则,将目标列映射到公有可读属性:
语句中指定的每一列都必须映射到类型兼容的公开 getter。POCO 行按传输块逐块转换,因此,即使先前的块 已经发送,后续块中的无效值仍可能尚未被发现。ClickHouseTcpNotMapped 可将 ImportBatch 等仅供应用程序使用的属性 排除在映射之外。

SQL 参数

原生客户端会将值绑定到 ClickHouse 原生的 {name:Type} 占位符,但不会重写 ADO.NET 风格的 @name 占位符。
每个参数都需要通过 SQL 占位符或 ClickHouseTcpParameter.ClickHouseType 属性指定类型。表名和列名应使用 Identifier 类型。 当 DateTime 或 DateTimeOffset 表示某个确切时刻时,应指定时区,例如 {timestamp:DateTime('UTC')};否则,服务器的会话时区可能会影响其含义。

查询 ID

每项操作都有一个查询 ID,ClickHouse 系统表、客户端日志和 trace span 都会用到它。当 QueryId 为 null 或空字符串时,客户端会生成一个新的 GUID。如果需要将操作与应用程序标识符 关联,请设置该 ID:

查询进度和元数据

设置 ClickHouseTcpQueryOptions.Callbacks,以接收在返回响应的过程中穿插传输的元数据:
进度值表示增量,而非累计值。回调按数据包顺序,在读取响应的 线程上同步执行;请确保回调执行迅速,且不要抛出异常。传给 OnLog、OnProfileEvents、OnTotals 和 OnExtremes 的块仅供借用,不得在 回调结束后继续使用。 对于通过插入操作上传的行,ClickHouse 不会发送查询进度数据包。可使用 OnBlockWritten 监测客户端发送的块;它反映的是传输进度,并不表示 服务器已提交插入操作。

取消操作

每项操作都支持传入 CancellationToken:
取消或放弃流式结果后,相关连接便不宜继续复用,因此客户端 会关闭该连接,而不是将其放回连接池。客户端仍可正常使用,后续操作会打开或 复用其他连接。如果会话固定使用该连接,取消操作就可能 终止会话,导致会话中的临时表和配置丢失。

服务器信息和健康检查

GetServerInfoAsync 返回原生握手过程中获取的服务器身份、版本、时区及协商后的协议信息:
根据 ProtocolRevision (客户端与服务器协商确定的修订版本) 判断传输层功能是否可用; 根据 Version 判断数据类型和函数等 SQL 功能是否可用。该方法在有可用连接时会复用现有 连接,连接池为空时则会新建连接。 使用 PingAsync 通过原生协议执行就绪检查:
Ping 成功说明原生协议连接和身份验证均正常,但无法确认是否有权访问 应用程序表。

更多示例

如需查看涵盖数据读取、插入、类型、会话、TLS、可观测性和错误处理的完整可运行示例,请参阅 GitHub 仓库中的 原生 TCP 示例。

会话

普通客户端操作会从连接池中获取任意可用连接,因此连接上的本地状态 既无法相互隔离,也不能保证在不同调用之间保留。连接池中的不同连接可能连接到不同的服务器。 如果临时表、SET 语句或其他一系列操作必须使用同一连接,请开启会话。
IClickHouseTcpSession 提供与客户端相同的查询、执行、插入、ping 和服务器信息 API。 会话不是线程安全的。会话操作必须依次进行:当前操作 尚未完成时发起另一项操作会被拒绝。流式结果会一直占用会话,直到结果被完整枚举或其 枚举器被释放。 每个会话在整个生命周期内都会占用一个 MaxPoolSize 槽位。客户端仍可使用连接池中的其他 连接,但如果连接池中只有一个连接,其他客户端操作就必须等待, 直到会话被释放或 PoolTimeout 超时。 请先释放会话,再释放其所属的客户端。释放会话时,会关闭该会话专用的 连接,而不是将其归还连接池,以免后续调用方继承该会话的 临时表或设置。连接或协议故障、操作被取消,或结果流未被完整读取, 都可能导致会话无法继续使用;当 IsOpen 变为 false 时,请创建新会话。

最佳实践

客户端生命周期与连接池

针对每组不同的端点和配置,创建一个 ClickHouseTcpClient 或 ClickHouseTcpDataSource,并在应用程序的整个生命周期内复用。客户端是线程安全的,且拥有自己的连接池;如果每次操作都新建客户端,就会反复创建连接池,无法复用连接。 在使用依赖注入的应用程序中,只需注册一次 AddClickHouseTcpDataSource,然后注入 IClickHouseTcpClient。客户端由服务提供者管理,因此使用注入客户端的代码不应自行释放该实例。未使用依赖注入时,应通过 await using 在应用程序关闭时释放客户端。 每个原生连接同一时间只能执行一项操作。因此,MaxPoolSize 既限制打开的连接数,也限制并发操作数;超出上限的调用方最多等待 PoolTimeout,直到有可用名额。设置该值时,既要满足应用程序的实际并发需求,也不能超过服务器端的连接限制和查询限制。调高该值不会加快单个查询。 还需留意以下占用连接池的情况:
  • 流式结果会持续占用连接,直到其枚举器被释放。
  • 会话在整个生命周期内占用一个连接,且同一时间只接受一项操作。
  • 如果放弃处理结果、取消正在执行的操作,或发生协议或连接故障,连接会被关闭,而不会被复用。
及时释放会话;对于异步结果,务必枚举完毕或释放其枚举器。可使用 ClickHouse.Driver.Tcp.Pool 日志类别排查连接池耗尽、连接意外关闭或频繁重新建立连接等问题。

超时与取消

TCP 客户端为不同的等待环节分别设置了超时限制: 如果从连接池获取连接时需要先等待可用槽位,再建立新连接,整个过程最多可能耗时 PoolTimeout + DialTimeout。每次从传输层读取数据时,ReadTimeout 都会重新计时,因此如果 长时间运行的查询持续返回进度信息或数据,其运行时间就可能超过该值。应将其设置得足够长,以容许服务器在正常情况下 暂时无响应;只有当其他超时设置能够可靠地限制整个操作的时长时,才将其设为 TimeSpan.Zero。 如果需要为整个操作设置超时,请使用取消令牌:
客户端会在确保安全的前提下尝试发送原生 Cancel 数据包,然后关闭响应尚未完全读取的 连接。连接池中的客户端后续仍可正常执行操作, 但取消操作可能导致绑定的会话无法继续使用。已取消或超时的插入操作 结果可能无法确定;请遵循插入重试指南,不要 盲目地重新执行。

日期和时间处理

时间戳优先使用 DateTime('UTC') 或 DateTime64(S, 'UTC'),应用程序代码中则使用 DateTimeOffset 或 Kind 为 Utc 的 DateTime。这样可以明确表示实际时间点, 避免依赖服务器时区或会话时区。 TCP 客户端写入 DateTime 值时遵循以下规则: 如果未指定时区的时间落在夏令时切换时跳过的时段内,该时间会被拒绝,因为它不对应任何 实际时间点。对于秋季时钟回拨造成的歧义时间,选择较早的那个时间点。如需明确指定, 请使用 DateTimeOffset。 对于表示某个时间点的 SQL 参数,请在占位符中包含时区:
进行列式读取时,DateTime 和 DateTime64 默认返回原始的 uint 或 long 计数值。 如果需要更方便处理的日期时间值,请使用 Block.ReadAs<DateTimeOffset>、IDateTimeColumn.GetDateTimeOffset 或对应的 POCO 属性。 如果必须保留每一位数字,则应使用原始计数值: DateTimeOffset 的分辨率为 100 纳秒,因此通过它读取标度为 8 或 9 的 DateTime64 会丢失不足一个 tick 的精度。 对于 Date 和 Date32,请使用 DateOnly。对于可能为负值或超过 24 小时的 Time 和 Time64 值, 请使用 TimeSpan;只有当列的取值限定为一天中的某个时刻时,才适合使用 TimeOnly。

流式处理与结果生命周期

使用 StreamAsync 和类型化列处理高吞吐量工作负载。这种方式符合 ClickHouse 的 列式布局,避免为每一行创建数组、进行装箱和分配 POCO 对象。如果拥有独立的应用程序模型比尽量减少内存分配更重要,请使用 QueryAsync<T>;只有在编译时无法确定 schema 时,才使用非类型化的 QueryAsync。 StreamAsync 返回的块是借用的。块、其中的列、复合子列以及 所有 span 都只在当前循环迭代期间有效。请就地处理,或复制需要 在本次迭代结束后继续使用的值:
不要自行释放迭代过程中返回的块,应由 await foreach 释放枚举器。如果只需要部分 查询结果,请在 SQL 中使用 LIMIT 限制返回量,并只选择所需的列。提前退出循环 会取消剩余响应,导致该连接无法复用。

插入形态与批处理

如果数据本身已按列组织,优先使用带类型化列的 InsertAsync,这样所需的 客户端转换最少。对于 POCO 数据,InsertRowsAsync<T> 更合适;无类型的 object[] 行最灵活,但需要分配数组,并对值类型进行装箱。 每次插入调用应发送适量的行,避免频繁发起小规模插入。在单次 调用中,MaxRowsPerBlock 会将数据拆分为原生传输格式的块。其默认值为 50,000 行,也 限制了转换行和 POCO 输入时使用的临时列缓冲区大小。增大该值可能 提高窄行的吞吐量,但也会增加峰值内存占用;将其设为 null 则会将整个 插入写为一个块,并一次性转换该块中所有按行组织的输入。 MaxSendBufferBytes 则独立限制刷写到 套接字之前所缓冲的已编码数据量。它是一个在处理列的间隙检查的软上限,因此单个宽列的数据量可能超过该值。 行和 POCO 输入会逐块转换。因此,后续块中的无效值可能要等到 前面的块已发送后才会被发现。如果数据质量无法保证,请在 插入前验证可空性和 CLR 类型;如果逻辑批次可能重试,还应设置根据数据生成的 DeduplicationToken。

支持的数据类型

下表区分了解码后的 IColumn<T> 直接返回的类型,以及通过 Block.ReadAs<T> 转换、QueryAsync<T> POCO 映射或插入操作支持的其他类型。你可以使用这些操作所用的同一个 解析器,查询是否支持特定的类型映射:
Nullable、Array、Tuple、Map 和 LowCardinality 的映射规则也会递归应用于其内部类型。

类型映射:从 ClickHouse 读取

Default .NET type 指 Block.Column<T> 和 IColumn<T> 对外提供的 T。Also readable as 列中列出的类型可通过 Block.ReadAs<T> 和 POCO 属性读取。

整数类型

浮点类型

Decimal 类型

ClickHouseTcpDecimal 使用任意大小的整数存储尾数,并存储标度,因此能够保留精度 超出 decimal 表示范围的值。

布尔类型

String 类型

如果数据可能不是有效的 UTF-8,请使用 IStringColumn.GetBytes(row),或将其读取为 byte[]。 FixedString(N) 会保留全部 N 个字节,包括末尾的零字节。

日期和时间类型

默认使用原始整数表示,以便按列读取时保留传输数据中的精确值。 转换为日期时间值时,使用类型声明的时区;如果类型未声明时区,则使用会话时区。 如果值为负数或不小于 24 小时,将 Time 或 Time64 读取为 TimeOnly 会抛出异常; 如需支持 ClickHouse 的完整取值范围,请使用 TimeSpan。

枚举类型

IEnumColumn 还提供已声明的标签与对应序数值之间的映射。

其他标量类型

复合类型

元组最多可包含七个元素。Nested 列的读取方式与字段数量无关,建议通过 INestedColumn 读取。该接口可访问带名称的扁平化字段列和共享的行偏移量,而无需 分配装箱记录。使用 ClickHouse 的默认设置 flatten_nested = 1 时,嵌套字段则会作为 名称中带点号的独立 Array(T) 列返回。

Variant、Dynamic 和 JSON 类型

Variant 和 Dynamic 返回每行所选类型对应的 CLR 值,或返回 null。它们的 列式视图提供判别值流,并为每种运行时类型提供一个指定类型的子列,从而避免 逐列处理数据时的装箱操作。 TCP 客户端通过 ClickHouse 的 String 序列化读取 JSON。客户端默认启用 output_format_native_write_json_as_string = 1;禁用此设置后,将无法读取 JSON。 ClickHouse 会解析并规范化 JSON,因此返回的文本在键的顺序、 空白字符和数字格式方面可能与插入的文本不同。

几何类型

Geometry 以包含上述六种几何类型的 IVariantColumn 形式呈现。

QBit 类型

TCP 客户端支持无步长的双参数形式 QBit(T, N),不支持带步长参数的 三参数形式。

AggregateFunction 类型

AggregateFunction 列存储函数专用的中间状态。请在服务器端将其计算为最终结果,例如使用 sumMerge(column),然后读取结果值。

类型映射:写入 ClickHouse

每个条目对应传入 InsertAsync 的 IColumn 中一行数据的 CLR 类型。类型映射 严格对应:TCP 客户端不会进行类似 Convert 的强制类型转换。

整数类型

浮点类型

写入 BFloat16 时,每个 float 值都会被截断为 16 位脑浮点数。

Decimal 类型

如果按标度调整后的尾数超过声明的精度,写入时会抛出 OverflowException。

布尔类型

String 类型

FixedString(N) 不接受 string,这是有意设计的;请显式编码并填充该值,以确保其 二进制表示准确无歧义。

日期和时间类型

直接以整数形式写入的值使用读取表中所列的单位。DateTimeOffset 保留原始时间点。 DateTime 的处理方式取决于其 Kind;当值为 Unspecified 时,会将其视为 列时区的挂钟时间;如果列未指定时区,则视为会话时区的挂钟时间。

枚举类型

未知标签会在发送数据前被拒绝。

其他标量类型

复合类型

可接受的数据形态按递归规则确定:Array(Nullable(DateTime)) 每行接受一个 DateTime?[], Map(String, UInt32) 每行接受一个 KeyValuePair<string, uint>[]。 直接写入 Nested 时,需使用先前读取操作返回的密集列数据形态。对于由应用程序构造的 插入操作,请使用默认的 flatten_nested = 1 表示形式,并将每个名称中带点号的 Array(T) 字段 分别写入独立的列。

Variant、Dynamic 和 JSON 类型

对于 Variant,运行时类型必须只与一个备选类型的默认 CLR 类型匹配。在变体类型中,不会使用将 DateTime 写入 DateTime 备选类型之类的便捷映射;应改用该备选类型的默认原始类型。Dynamic 支持标量值,以及受递归支持的数组、映射和元组值。

几何类型

对于应用程序构建的 Geometry 列,只有 Point 和 MultiPolygon 具有独特的 CLR 形态。 Ring 与 LineString 的形态相同,Polygon 与 MultiLineString 也是如此,因此客户端无法 推断 object 值对应哪一种类型。解码后的 Geometry 列会保留判别值, 因此再次插入时不会产生这种歧义。

QBit 类型

AggregateFunction 类型

压缩

客户端默认启用压缩,对写入的块使用 LZ4;ClickHouse 默认也对响应块 使用 LZ4。压缩适用于结果块和插入操作上传的块, 而查询文本、进度信息、异常及其他协议数据包仍采用常规传输编码。 使用连接字符串中的 Compression 键指定客户端使用的编解码器:
与之等效的强类型配置使用 IClickHouseCompressor:
将 Compressor = null 设为禁用压缩。自定义压缩器必须支持原生块的 帧信息;仅支持 HTTP 消息体的压缩器会在创建客户端时被拒绝。

选择响应编解码器

Compressor 决定客户端写入的块如何压缩。原生 Query 数据包会请求 服务器启用压缩,但不会指定编解码器,因此 ClickHouse 会根据 network_compression_method 设置选择响应编解码器。服务器默认使用 LZ4。 如有需要,可为每次操作单独设置响应编解码器:
LZ4 通常占用的 CPU 资源更少,是高速网络环境下的最佳默认选择。ZSTD 通常生成 更小的数据帧,但 CPU 开销略高。如果服务器位于同一主机上,或通过其他极高速链路连接,禁用压缩可能更合适。 更改默认设置前,请使用有代表性的数据进行测试。

错误处理

ClickHouse 或原生连接报告的故障所对应的异常均继承自 ClickHouseTcpException, 后者又继承自 DbException: ClickHouseTcpServerException 提供 RawCode (服务器发送的原始数值错误码) , 以及 Code (驱动程序为该错误码定义了名称时,对应的 ClickHouseErrorCode) 。 它还包含服务器异常的 Name、ServerStackTrace,以及通过 InnerException 提供的所有嵌套服务器异常。
未被 ClickHouseErrorCode 涵盖的错误代码,其 Code == ClickHouseErrorCode.Unknown;请检查 RawCode,不要认为该枚举包含所有 ClickHouse 错误。 参数验证和对象生命周期相关的错误仍使用常规的 .NET 异常类型。 取消操作会引发 OperationCanceledException。DialTimeout 和 ReadTimeout 会引发 ClickHouseTcpConnectionException,其内部异常为 TimeoutException,并且连接会被弃用。 PoolTimeout 会引发普通的 TimeoutException,而非 ClickHouseTcpException:这并不表示连接 发生故障;可通过减少并发操作、扩大连接池或完成结果枚举来解决。

重试操作

插入过程中如果发生连接故障或超时,操作结果便无法确定: 客户端未收到响应时,服务器可能已经提交了这些行。重试前,请为该逻辑批次设置固定的 DeduplicationToken:
重试同一批次时,使用相同的令牌;处理新数据时,则使用不同的令牌。对于其他 操作,只有在语句可以安全地重复执行,且相应故障属于暂时性故障时,才应重试; 大多数服务器错误需要修改查询或配置,而不是重试。

日志与诊断

TCP 客户端与 Microsoft.Extensions.Logging 集成。日志功能是可选的;未提供 LoggerFactory 时,客户端不会创建日志记录器,也不会格式化日志消息。
通过依赖注入扩展注册客户端时,如果传入的选项未指定日志记录器工厂,客户端就会使用容器提供的 ILoggerFactory。

日志类别

通过标准的 .NET 日志配置设置这些类别:
操作开始消息以 Debug 级别记录,最多包含 StatementMaxLength 个字符的 SQL 语句。默认长度为五个字符。将该值设为零或负数,可避免在日志中记录 SQL 语句; 仅在考虑参数和字面量可能包含敏感数据后,才应增大该值。

服务器日志消息

日志集成用于报告客户端行为。ClickHouse 自身的查询日志数据包则另行传递给 ClickHouseTcpQueryCallbacks.OnLog。可通过 send_logs_level 服务器设置启用这些数据包:
日志块仅供临时借用,回调返回时便会释放。请同步处理该日志块,或 复制需要保留的值。回调应尽快执行,且不要抛出异常;回调抛出的异常会 终止当前操作并断开连接。

OpenTelemetry

TCP 客户端通过 .NET 的 System.Diagnostics.Activity API 发出分布式追踪 span。 使用 OpenTelemetry 追踪器提供者订阅 TCP 活动源:
如果使用控制台应用程序或手动配置:
活动源名称为 ClickHouse.Driver.Tcp,与 HTTP 客户端的活动源分开,因此 可以独立采集任一传输方式的活动。如果没有监听器订阅,操作就不会 产生活动。

Span 和属性

客户端会为 SQL 操作、ping 和新建连接生成 span。SQL span 以语句开头的关键字命名, 例如 SELECT 或 INSERT;如果无法识别语句开头的关键字,则使用 query。 connect span 涵盖套接字连接、TLS 协商和原生协议握手。语句 span 还包含等待 连接池中可用连接的时间。 仅当服务器或客户端有实际值可报告时,才会提供相应的计数器。例如, 通过上传数据块执行的插入操作会报告客户端发送的行数,但不会收到服务器的 读取计数器。失败的 span 还包含一个 exception 事件,记录异常类型、消息和堆栈跟踪。

SQL 文本和敏感数据

默认情况下,span 中不包含 SQL 文本。如需包含,请显式启用并设置合适的长度限制:
StatementMaxLength 同时限制 db.query.text 属性和调试日志中 SQL 文本的最大长度。设为零 或负数时,两者均不包含 SQL 文本。还应检查导出的异常消息和堆栈跟踪,因为 服务器错误可能包含查询详情。

trace 上下文传播

当 Activity.Current 使用 W3C ID 格式时,客户端会将 trace ID、span ID、trace 标志 和 trace 状态写入原生 Query 数据包。ClickHouse 随后便可在同一条 分布式 trace 中创建服务器 span。服务器端的 span 收集与采样仍由 ClickHouse 的配置决定;例 如,可为每次查询单独设置采样概率:
传递过来的查询 ID 可与 trace ID 配合使用,将客户端 span 与 system.query_log 关联起来;如果已配置,还可与 ClickHouse 的 system.opentelemetry_span_log 关联。

限制

TCP 客户端仍处于实验阶段,尚不支持 HTTP 客户端的全部功能。

仅限 HTTP 的功能

TCP API 不是 ADO.NET 提供程序,无法与 ORM 配合使用。它也不支持 CSV、JSONEachRow、Parquet 或原始流的输入和输出;Bearer 身份验证或自定义 HTTP 请求头;自定义参数类型解析、格式化或读取转换;也不支持为单次查询指定数据库 或角色。如果应用程序需要上述任何功能,请使用 HTTP 客户端。

SQL 参数

仅支持 ClickHouse 原生的 {name:Type} 占位符;TCP 客户端不会将 ADO.NET 风格的 @name 占位符改写为原生格式。在 ClickHouse 25.8 至 26.6 版本中,与 服务器配置项同名的参数 (如 limit 或 offset) 可能被当作配置项而被拒绝。若需兼容这些版本, 请更改参数名,例如改为 row_limit。

数据类型覆盖范围

支持的数据类型一节列出了完整的映射关系。 当前原生编解码器主要有以下限制:
  • Tuple 最多可包含七个元素。
  • 不支持 AggregateFunction(function, ...) 的中间状态。请先在 服务器端完成最终计算 (例如使用 sumMerge(column)) ,再读取结果。
  • 仅支持双参数、无步长的 QBit(T, N) 布局。
  • JSON 使用 ClickHouse 的 String 序列化方式,要求设置 output_format_native_write_json_as_string = 1;客户端默认会请求启用此设置。
  • 当 flatten_nested = 0 时,应用程序构造的插入数据不支持 Nested(...) 的按行组织形式。 请改用默认展平、以点号分隔的 Array(T) 列。
  • 如果不同几何类型具有相同的 CLR 结构 (例如 Ring 和 LineString) ,应用程序构造的 Geometry 列就无法推断具体类型。 请插入具体的几何类型,或重新插入保留了判别值的已解码 Geometry 列。

自动重试

客户端不会自动重试失败的操作。只重试可安全地重复执行的操作;如果插入操作的结果尚不明确,应在重试前设置一个保持不变的 DeduplicationToken。 详情请参阅重试操作。
最后修改于 2026年10月14日