必须显式关闭TLS和压缩、用clickhouse.Open替代sql.Open、批量写调batch.Send()、Nullable字段用sql.NullString或ch.String、结构体ch标签严格匹配列名大小写。

连不上、写得慢、读出来是零值——不是 ClickHouse 不行,而是 clickhouse-go/v2 的默认行为和你的开发环境不匹配。必须显式关闭 TLS 和压缩,用 clickhouse.Open 替代 sql.Open,批量写必须调 batch.Send(),否则日志写入延迟高、查询结果丢字段,都是配置惯性导致的。
连接失败:secure=false 和 compress=false 必须显式设
本地开发时,ClickHouse 默认只开 TCP 端口 9000 且无 TLS/压缩,但 clickhouse-go/v2 默认启用 Secure: true 和 CompressionLZ4。不关就会卡在握手或报 Code: 287. DB::Exception: Unknown compression method。
-
Addr必须是[]string{"127.0.0.1:9000"},不能是字符串或含http://前缀 - DSN 示例:
tcp://127.0.0.1:9000?database=default&secure=false&compress=false - 若用
clickhouse.Options{}构造,需明确写Secure: false和Compression: &clickhouse.Compression{Method: clickhouse.CompressionNone} - 错误现象:日志只显示
context deadline exceeded,无 TLS 或压缩提示——这是典型静默失败
批量写入日志:别用 stmt.Exec,改用 PrepareBatch + Append + Send
微服务每秒可能产生数千条日志,用循环 stmt.Exec() 插单行,网络往返+SQL 解析开销直接拖垮吞吐。ClickHouse 的设计前提是“大块写入”,batch.Send() 才真正触发传输。
- 必须按列传参:
batch.Append([]string{...}, []int64{...}, []time.Time{...}),不能传 struct 实例 - 每批控制在
10000–100000行之间;太小(如 100)仍高频交互,太大(如 500000)易被服务端max_insert_block_size拒绝 - 漏掉
batch.Send()就等于没发——Append()只是内存攒数据 - 可加
async_insert=true参数缓解阻塞,但异步失败不立即反馈,仅适合非关键路径
读取日志字段:Nullable 类型必须用 sql.NullString 或 ch.String
日志表中 message、trace_id 等字段常定义为 Nullable(String),Go 层若用 *string 接收,遇到 NULL 就 panic:cannot scan into *string。
立即学习“go语言免费学习笔记(深入)”;
- 查出来的
Nullable(String)字段,必须用sql.NullString或ch.String(需import "github.com/ClickHouse/clickhouse-go/v2") - 结构体字段带
ch:"column_name"标签才生效;json:、db:标签完全被忽略 - 大小写敏感:ClickHouse 列是
log_level,结构体字段必须写LogLevel string `ch:"log_level"`,写成`ch:"Log_level"`就永远是空字符串 - 时间字段建议统一转 UTC:
ts.UTC()写入,WHERE 条件用字符串格式(如'2026-07-01 12:00:00'),避免时区错位
ScanStruct 字段为零值却不报错:ch 标签拼错就沉默跳过
rows.ScanStruct(&logEntry) 是常用写法,但它对标签极其苛刻:拼错、漏写、大小写不一致,它不会 panic,也不会 warn,只是默默跳过该字段,留一个零值。
- 检查方式:打印
rows.Columns()看实际返回列名,再逐个比对结构体ch标签 - 常见陷阱:
user_id→UserID int64 `ch:"user_id"`,不能写成`ch:"User_id"`或`ch:"userid"` - 如果表结构含嵌套类型(如
Array(String)),ScanStruct不支持,必须用rows.Scan()手动解包 - 空结果判断不能只看
rows.Next() == false,务必检查rows.Err()是否为 nil —— 否则 SQL 错误会被掩盖
最麻烦的不是语法,而是那些不报错的行为:标签少一个下划线、压缩没关、batch.Send() 忘了调……它们都安静地让日志查不到、写不进、字段为空。上线前至少跑一遍真实日志流,验证从写入到聚合查询的全链路是否真正通了。


















