流式查询必须用 HTTP + stream=1,因 clickhouse-go/v2 的 Query 方法默认缓冲全量结果致 OOM;需直接 http.Client 发 GET 请求并显式添加 stream=1、format=JSONEachRow 和 compress=false;时间处理须全程绑定 time.UTC 避免时区偏移。

流式查询必须用 HTTP + stream=1,别碰 clickhouse-go 的 Query 方法
clickhouse-go/v2 的 Query 方法在默认 TCP 协议下会把整张结果集缓冲进内存,百万行日志一查就 OOM。这不是你 SQL 写得差,是驱动底层没暴露 reader,也没法控制 chunk 大小。
真正能流式读的路径只有一条:绕过 driver 封装,直接用 http.Client 发 GET 请求,URL 中必须带 stream=1 和 format=JSONEachRow:
http://user:pass@localhost:8123/?database=default&query=SELECT%20ts,%20level,%20msg%20FROM%20logs%20WHERE%20ts%3E=%272024-01-01%27&stream=1&format=JSONEachRow
-
compress=false必须显式加在 DSN 或 URL query 里,否则 Go 的http.Transport自动解压会破坏 ClickHouse 的 LZ4 流格式 - 别用
db.QueryRow()或rows.Columns()探测 schema —— HTTP 模式下这些调用可能触发全量拉取,直接废掉流式本意 - 设置
Timeout和KeepAlive:ClickHouse 在stream=1下连接可能挂住几十秒,http.DefaultClient的 30s timeout 会导致中断
DateTime64 查询漏数据?时间构造必须绑定 UTC Location
ClickHouse 的 DateTime64(3, 'UTC') 字段,如果 Go 里用 time.Now().UTC().Format(...) 拼字符串再塞进 SQL,或用 time.Parse 解析但没指定 time.UTC,就会按本地时区解释,造成 where 条件偏移几小时,漏掉大量日志。
正确做法是所有时间变量从源头就绑定 time.UTC:
立即学习“go语言免费学习笔记(深入)”;
t := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
sql := fmt.Sprintf("SELECT * FROM logs WHERE ts >= toDateTime64('%s', 3, 'UTC')", t.Format("2006-01-02 15:04:05.000"))- 写入时也统一用
toUnixTimestamp64Milli(now64()),避免客户端时区干扰 - 别用
BETWEEN:ClickHouse 对DateTime64的BETWEEN有隐式截断,改用ts >= ... AND ts - 字段类型必须匹配:Go 中接收时间列要用
time.Time,且 scan 前确保rows.Scan(&t)的&t是time.Time类型指针,不是*time.Time
批量插入慢?PrepareBatch 是唯一靠谱路径
用 stmt.Exec() 单条插、或拼大 INSERT 字符串,会让 ClickHouse 无法合并写入批次,变成随机小写,CPU 和网络打满但吞吐上不去。
必须用 PrepareBatch 构建批处理上下文:
batch, err := conn.PrepareBatch(ctx, "INSERT INTO logs (ts, level, msg) VALUES (?, ?, ?)")
for _, log := range logs {
batch.Append(log.Ts, log.Level, log.Msg)
}
err = batch.Send()- 每批建议 1000–10000 行,太小起不到批处理效果,太大可能触发服务端内存限制
- 别在循环里反复调用
conn.Prepare()——PrepareBatch内部已复用 statement,重复 prepare 反而增加开销 - 表引擎必须是
MergeTree系列,ReplacingMergeTree或SummingMergeTree才能发挥合并优势;Memory引擎不适用于持久化日志场景
连接连不上?先看协议和端口是否对得上
八成连不上是因为协议错配:clickhouse-go 默认尝试 HTTP(8123 端口),但生产环境通常只开放 TCP(9000 端口)且禁用 HTTP。
确认方式很简单:
- 查服务端
/etc/clickhouse-server/config.xml,看<http_port>和<tcp_port>是否启用;线上环境多数只开<tcp_port>9000</tcp_port> - 客户端 DSN 改用
tcp://前缀:tcp://127.0.0.1:9000?database=default&username=default&password= - 若必须走 HTTP,DSN 用
http://,并加&compress=true减少传输量;新版驱动默认启用 TLS,没证书要加&secure=false&insecure=true
最隐蔽的坑是字段类型和 Scan 顺序不一致 —— ClickHouse 返回列顺序严格按 SELECT 字段顺序,rows.Scan(&a, &b, &c) 必须跟 SQL 里字段顺序完全一致,且类型一一对应,NULL 字段还得用 *string 接收,不然直接 panic。


















