必须用 clickhouse.open 而非 sql.open;addr 必须为字符串切片;端口、协议、压缩需与服务端严格对齐;批量写必须调用 batch.send()。

别用 sql.Open 连 ClickHouse,v2 驱动根本不注册 driver;连不上八成是协议/端口/压缩三者之一没对齐;批量写不调 batch.Send() 就等于没发。
clickhouse.Open 连接失败的三个硬性条件
新版 clickhouse-go/v2 不走 database/sql 接口,sql.Open("clickhouse", ...) 必 panic。必须显式调用 clickhouse.Open(&clickhouse.Options{...})。
-
Addr必须是字符串切片,例如[]string{"127.0.0.1:9000"};写成"127.0.0.1:9000"会编译报错 - 若服务端只开了 TCP(
tcp_port=9000),但你填了8123,或 DSN 用了http://前缀——连接直接静默超时,错误里不提端口问题 - TLS 默认开启:哪怕本地单机开发,也得显式加
Secure: false,否则握手卡住,日志只显示context deadline exceeded - 压缩不匹配也会失败:服务端禁用了 LZ4(查
SELECT * FROM system.settings WHERE name = 'network_compression_method'),但客户端没关Compression: &clickhouse.Compression{Method: clickhouse.CompressionLZ4},就会报Code: 287. DB::Exception: Unknown compression method
INSERT 百万行慢?不是 ClickHouse 慢,是你没用对 batch
用 stmt.Exec() 循环插 10 万行,实测耗时 30+ 秒;换成 conn.PrepareBatch() + batch.Append() + batch.Send(),通常压到 800ms 内。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
-
batch.Append()只是内存攒数据,不发包;漏掉batch.Send()就等于白干 - 每批行数建议控制在
10000–100000:太小(如 100 行)触发高频小块写入;太大(如 50 万)易被服务端max_insert_block_size拒绝或 OOM - 别传 struct 实例:
batch.Append(myStruct)会 panic;必须按列拆成[]int64、[]string等切片,再分别Append() - 时间字段务必用
time.Time,且写入前统一转 UTC:ts.UTC();否则时区错位导致 GROUP BY 或 WHERE 失效
ScanStruct 字段为零值却不报错?ch 标签拼错了
rows.ScanStruct(&s) 完全依赖结构体字段上的 ch tag,大小写、下划线、拼写差一个字符,就跳过该字段,给零值——不 panic,不 warn,只沉默。
- ClickHouse 列名是
user_id,结构体字段必须写UserID int64 `ch:"user_id"`;写成`ch:"User_id"`或漏 tag,UserID就永远是0 -
ch标签不认json:、db:、sql:,写了也白写 - Nullable(String) 类型字段,不能用
*string;要用sql.NullString或ch.String(后者来自github.com/ClickHouse/clickhouse-go/v2/lib/columns) - 字段顺序无关,但 tag 名必须和表定义完全一致(包括反引号包裹的标识符)
Query 返回空结果却没报错?rows.Err() 被你忽略了
这是最隐蔽的“假成功”:SQL 执行了,没语法错误,rows.Next() 返回 false,你以为没数据,其实是扫描中途出错后提前退出。
- 必须在
for rows.Next()循环结束后,立刻检查if err := rows.Err(); err != nil;很多 “空结果” 其实是类型不匹配或 NULL 解包失败 - 别用
SELECT *:字段顺序一变,rows.Scan(&a, &b, &c)就全乱;明确写出字段名,并严格按建表顺序声明变量 - 字符串字段建议用
*string接收,避免 NULL 触发 panic;数字字段用sql.NullInt64等类型兜底 - 大结果集记得设 context 超时:
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second),否则查询卡死无感知
复杂点不在语法,而在协议细节与行为惯性:TCP 还是 HTTP、LZ4 开没开、tag 少个字母、Send 忘调——这些地方一错,现象全是“连不上”“没数据”“好慢”,但错误信息几乎不指向根因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










