ClickHouse 连接失败主因是驱动默认启用 TLS+LZ4 而服务端未配置,需显式设 secure=false&compress=false;批量插入须用 PrepareBatch 按列攒批;ScanStruct 需严格匹配 ch: 标签及 Nullable 类型。

连不上 ClickHouse,八成不是网络或密码错了,而是 clickhouse-go/v2 默认走 TLS + LZ4 压缩的 TCP 连接,而你本地 docker run yandex/clickhouse-server 起的服务只监听裸 TCP(9000)或 HTTP(8123),根本没配证书、也没开 LZ4 解压——协议直接不匹配,握手卡死,日志里只报 context deadline exceeded。
tcp://127.0.0.1:9000?secure=false&compress=false 是刚需
用 TCP 协议(端口 9000)是 v2 驱动唯一原生支持的方式,但必须显式关掉 TLS 和压缩:
-
Addr必须写成[]string{"localhost:9000"},不能写"localhost:8123"—— 后者是 HTTP 端口,v2 不认 -
secure=false和compress=false必须出现在 DSN 或 Options 里,缺一不可;否则驱动会尝试 TLS 握手,服务端直接不响应 - 如果服务端确实开了 LZ4(查
SELECT * FROM system.settings WHERE name LIKE '%compression%'),那客户端才可设compress=true,否则报Code: 287. DB::Exception: Unknown compression method
INSERT 百万行别用 db.Exec("INSERT ...", x, y)
单条 Exec 本质是发百万次小包,每条都触发 ClickHouse 的 minor merge,性能差十倍不止。真批量必须走列式攒批:
- 调
conn.PrepareBatch(ctx, "INSERT INTO t")拿*clickhouse.Batch,不是conn.Prepare() -
batch.Append()只是内存攒数据,不发包;攒够再调batch.Send()才真正提交 - 每批建议 1000–10000 行:太少交互频繁,太大易触发服务端
max_insert_block_size或 OOM - 别传
[]struct{},要按列拆:比如两列id, name,就得分别调batch.Column(0).Append([]int64{...})和batch.Column(1).Append([]string{...})
ScanStruct 字段不赋值?检查 ch: 标签和 Nullable 类型
rows.ScanStruct(&s) 不报错但字段全是零值,大概率是标签或类型对不上:
- 结构体字段必须带
ch:"column_name",大小写、拼写必须和表定义**完全一致**;json:、db:标签全无效 -
Nullable(String)不能扫进*string,会 panic:cannot scan into *string;得用sql.NullString或ch.String(需import "github.com/ClickHouse/clickhouse-go/v2/lib/columns") -
Nullable(UInt64)对应*uint64,不是uint64;漏了星号就 panic - 循环读取后务必加
if err := rows.Err(); err != nil { ... },很多“空结果”其实是扫描中途出错被静默吞了
Query 返回空但 rows.Err() == nil?先看 SQL 和字段顺序
ClickHouse 的 SELECT 在 Go 里不是“执行完就拿到结果”,而是流式拉取,容易漏掉关键检查点:
- 别依赖
SELECT *;明确写出字段名,并严格按顺序传给rows.Scan(&a, &b, &c),顺序错一位就全乱 - 时间字段如
DateTime64接收为time.Time没问题,但注意服务端时区(通常是 UTC),time.Now()是本地时区,比较前得统一 - 字符串字段建议用
*string接收,避免 NULL 导致 panic;若确定非空,建表时就该定义为String,而不是靠应用层补判空 - 分布式表查
GROUP BY结果不准?不是客户端问题,加SETTINGS distributed_group_by_no_merge = 0强制服务端做最终聚合
最常被忽略的是:连接成功 ≠ 连接可用。sql.Open 或 clickhouse.Open 返回 nil 错误,不代表能通;必须立刻调 db.Ping() 或 conn.Ping(ctx),否则问题会拖到第一次 Exec 或 Query 才暴露,且错误信息更难定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











