连接
下面的示例会返回 ClickHouse 服务器版本,并演示如何连接到 ClickHouse——前提是 ClickHouse 未启用安全保护,且可使用默认用户default 访问。
请注意,我们使用默认的原生端口进行连接。
conn 变量。
执行
可以通过Exec 方法执行任意语句。这适用于 DDL 和简单语句。不建议将其用于较大的插入操作或查询结果迭代。
批次插入
要插入大量行,客户端提供了批次处理机制。这需要先准备一个批次,然后即可向其中追加行,最后通过Send() 方法发送。在执行 Send 之前,批次数据会一直保存在内存中。
建议对批次调用 Close,以避免连接泄漏。这可以在准备完批次后通过 defer 关键字实现。如果始终没有调用 Send,它会负责清理连接。请注意,如果没有追加任何行,查询日志中会显示 0 行插入记录。
临时列
临时列是只写列,只在写入期间存在——不会被存储,也无法被查询。它们可用于在写入时计算派生列的值。查询行
你既可以使用QueryRow 方法查询单行,也可以通过 Query 获取用于遍历结果集的游标。前者接受一个目标端,用于将数据序列化到其中;而后者则需要对每一行调用 Scan。
SELECT 语句中指定的顺序传入;如果使用如上所示的 SELECT *,默认会采用列声明顺序。
与插入类似,Scan 方法要求目标变量具有合适的类型。这里同样尽量保持灵活性:在不会造成精度损失的前提下,会尽可能进行类型转换。例如,上面的示例展示了将 UUID 列读取到字符串变量中。有关每种列类型支持的完整 Go 类型列表,请参阅类型转换。
最后,请注意,Query 和 QueryRow 方法支持传入 Context。这可用于设置查询级别的参数;更多详情请参阅使用 Context。
异步插入
支持通过 Async 方法进行异步插入。这样,用户可以指定客户端是等待服务器完成插入,还是在收到数据后立即返回响应。这实际上控制的是参数 wait_for_async_insert。列式插入
支持按列格式插入。如果数据本身已经是按这种结构组织的,则无需再转换为行格式,因此可以提升性能。使用结构体
对于用户来说,Golang 结构体是 ClickHouse 中一行数据的逻辑表示。为便于处理,原生接口提供了几个便捷函数。配合序列化使用 Select
Select 方法支持通过一次调用,将一组返回行编组为结构体切片。扫描结构体
ScanStruct 可将查询返回的单行数据映射到结构体中。
追加结构体
AppendStruct 允许将结构体追加到现有批次中,并将其视为完整的一行。这要求结构体中的列在名称和类型上都与表一致。虽然所有列都必须有对应的结构体字段,但某些结构体字段可能没有对应的列表达形式。这些字段会被忽略。
服务器端查询参数
ClickHouse 服务器端查询参数使用{name:Type} 形式的类型化占位符。查询与参数值分开发送,因此参数值不会被插入到 SQL 文本中。
使用 ClickHouse API 时,请通过查询上下文传递参数:
clickhouse.Named 提供参数值:
clickhouse.Named 同样支持客户端的 @name 绑定语法;具体采用哪种机制,取决于查询中使用的占位符 ({name:Type} 或 @name) 。
String 值与转义序列
字符串值如何解释,取决于其传入方式。 通过clickhouse.Named (或 sql.Named) 传入 string/*string 或 []byte/*[]byte 值时,Go 值会被直接视为参数的字面值。驱动程序会自动转义控制字符,因此无论使用哪种协议,该值都能逐字节无损地往返传输。值中包含实际的换行符或制表符也没有问题:
clickhouse.Parameters / WithParameters 中始终包含已按 ClickHouse 的 Escaped 格式序列化的参数文本,不会自动转义。ClickHouse 在解析参数时会解码反斜杠转义序列:\n (换行符) 、\t (制表符) 、\r (回车符) 、\0 (空字节) 以及 \\ (字面反斜杠) 。对于 Nullable 类型,\N 是表示整个值为 NULL 的标记。
Go 会在 clickhouse-go 拿到解释型字符串字面量 ("...") 之前先对其进行处理,而原始字符串字面量 (`...`) 则会保留反斜杠。对于 Parameters,请按如下方式编写参数文本:
构造
Parameters 时,请勿使用 "line 1\nline 2" 或 "column 1\tcolumn 2",因为这些 Go 字面量中已经包含真正的换行符或制表符。在 ClickHouse 的 Escaped 格式中,真正的换行符和制表符字节用作字段分隔符,因此参数无法被完整解析,服务器会拒绝该参数。如果希望将 Go 字符串按字面值处理,请改用 Named。
参数值不是 SQL 字面量:通过 Named 传递顶层 String 时,传入的值不应带 SQL 引号——应使用 hello,而不是 'hello',否则引号字符会成为值的一部分。复合类型仍使用其文本语法,例如 Array(String) 写作 ['a', 'b'];只要文本中不含反斜杠转义或原始控制字符,同样可以通过 Named 传递。如果复合文本中的嵌套字符串值需要使用 \' 转义序列,则应改为通过 Parameters 传递。
完整的原生 API 示例
参数绑定
客户端支持在Exec、Query 和 QueryRow 方法中绑定参数。如下面的示例所示,支持命名参数、编号参数和位置参数。下文将分别给出示例。
特殊情况
默认情况下,如果将切片作为查询参数传入,它们会展开为以逗号分隔的值列表。如果需要注入一组用[ ] 包裹的值,应使用 ArraySet。
如果需要组/元组,并且要用 ( ) 包裹,例如用于 IN 运算符,可以使用 GroupSet。这在需要多个组的场景中尤其有用,如下例所示。
最后,DateTime64 字段需要指定精度,才能确保参数被正确渲染。不过,客户端并不知道该字段的精度 level,因此必须由用户提供。为简化这一操作,我们提供了 DateNamed 参数。
使用上下文
Go 的上下文可用于在 API 边界之间传递截止时间、取消信号以及其他请求范围内的值。连接上的所有方法都将上下文作为第一个参数。虽然前面的示例使用的是 context.Background(),但你也可以利用这一能力传递设置和截止时间,以及取消查询。 传入通过withDeadline 创建的上下文后,可以为查询设置执行时间限制。请注意,这是一个绝对时间;超时后只会释放连接并向 ClickHouse 发送取消信号。或者,也可以使用 WithCancel 显式取消查询。
辅助函数 clickhouse.WithQueryID 和 clickhouse.WithQuotaKey 可用于指定 Query id 和配额键。Query id 可用于在日志中跟踪查询以及执行取消操作。配额键可用于基于唯一键值对 ClickHouse 的使用施加限制,更多详情请参见配额管理。
你还可以使用上下文,确保某项设置仅应用于特定查询,而不是像连接设置中所示那样应用于整个连接。
最后,你可以通过 clickhouse.WithBlockSize 控制块缓冲区的大小。它会覆盖连接级别的设置 BlockBufferSize,并控制任意时刻解码并保存在内存中的最大块数。更大的值可能意味着更高的并行度,但代价是占用更多内存。
上述内容的示例如下。
Progress、profile 和日志信息
查询时可以请求 Progress、Profile 和日志信息。Progress 信息会报告在 ClickHouse 中已读取和处理的行数及字节数等统计数据。相应地,Profile 信息会汇总返回给客户端的数据,包括字节总量 (未压缩) 、行数和块数。最后,日志信息会提供有关线程的统计信息,例如内存使用情况和数据处理速度。 要获取这些信息,用户需要使用 Context,并向其传递回调函数。动态扫描
您可能需要读取某些表,但并不清楚它们的 schema,或返回字段的类型。这在进行临时数据分析或编写通用工具时很常见。为此,查询响应中会提供列类型信息。可以结合 Go 反射,按正确类型在运行时创建变量实例,并将其传递给 Scan。外部表
外部表 允许客户端在执行SELECT 查询时将数据发送到 ClickHouse。这些数据会被放入临时表中,并可在查询本身中用于求值。
要随查询发送外部数据,用户必须先通过 ext.NewTable 构建外部表,然后再通过上下文传递它。
OpenTelemetry
ClickHouse 在 TCP 和 HTTP 传输方式下都支持 trace 上下文传播。使用 TCP 时,客户端会将 span 序列化到原生二进制协议中。可使用clickhouse.WithSpan 通过上下文将 span 附加到查询。
HTTP 传输限制虽然 ClickHouse server 接受标准的
traceparent / tracestate HTTP 请求头,但 clickhouse-go 的 HTTP 传输目前不会发送这些请求头,因此 WithSpan 在 HTTP 下不起作用。作为变通方案,你可以通过连接选项中的 HttpHeaders 手动设置请求头。