QuestDB 不支持 PostgreSQL wire protocol 写入,仅通过 HTTP REST 或 InfluxDB Line Protocol(ILP)交互;pg_wire 端口仅限只读 SELECT,写入必须用 ILP,推荐官方 go-questdb-client 并协同调优 batch size、buffer size 与 uncommitted rows。

QuestDB 不是 PostgreSQL,不能直接用 sql.Open("postgres", ...) 连;它不支持标准 JDBC/SQL driver 协议,必须走 HTTP REST 或 InfluxDB Line Protocol(ILP)——后者才是写入性能的正确打开方式。
为什么不能用 database/sql + pq/pgx 连 QuestDB
QuestDB 没有实现 PostgreSQL wire protocol 的服务端逻辑。虽然它暴露了 PostgreSQL 兼容的查询端口(默认 8812),但仅限于只读 SQL 查询(SELECT),且需启用 pg_wire_enabled = true 配置;它不支持 INSERT、事务、预编译语句等任何写操作。尝试用 pgx.Connect 写数据会静默失败或报 ERROR: cannot insert into read-only table。
- PostgreSQL 协议只用于查询,不是“兼容替代”,本质是只读代理层
-
database/sql的连接池机制(如SetMaxOpenConns)对 ILP 写入完全无效——ILP 是无状态 TCP 流,不走连接池 - 用 pgx 执行
INSERT会直接报错,因为 QuestDB 的 pg wire 层根本不解析写入语句
写入必须用 ILP:Go 客户端选型与实操
QuestDB 原生高性能写入路径是 InfluxDB Line Protocol(ILP),基于裸 TCP,无 JSON 序列化开销,单连接轻松跑满千兆网卡。Go 生态中推荐两个轻量方案:
-
questdb/go-questdb-client(官方维护):封装 ILP,自动重连、批处理、gzip 可选,API 简洁 - 手写
net.Conn+bufio.Writer:适合极致控制(如固定 buffer 大小、避免 goroutine 泄漏),但需自行处理断线重试和背压
示例(使用官方 client):
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
client, err := questdb.NewClient(questdb.WithAddress("localhost:9009"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
// 构造 ILP 行:measurement,tag=value field=value timestamp
line := fmt.Sprintf("sensor,device_id=s1 temperature=%.2f,humidity=%.2f %d",
rand.Float64()*30+20,
rand.Float64()*50+30,
time.Now().UnixNano(),
)
if err := client.WriteLine(line); err != nil {
log.Printf("write failed: %v", err)
}
- 不要在循环里频繁
NewClient:每个 client 维护一个长连接,复用它 - 批量写入优于单行:调用
client.WriteLineBatch([]string{...}),减少 syscall 和网络往返 - 时间戳单位必须是纳秒(
time.Now().UnixNano()),QuestDB 默认按 nanosecond 解析 ILP 时间字段
查询用 REST API:绕过 database/sql 的合理姿势
需要执行 SELECT 时,用 HTTP REST(POST /exec)最稳妥。QuestDB 的 REST 接口返回的是 CSV 或 JSON,无需 driver 封装,也规避了 database/sql 的类型映射陷阱(比如 TIMESTAMP 列被当成 string)。
- 用
net/http+bytes.NewReader发送query=SELECT%20*%20FROM%20sensor_readings%20WHERE%20reading_time%20%3E%3D%20'2026-06-30' - 响应体直接
csv.NewReader(resp.Body)解析,比 JSON 更快更省内存 - 若需参数化,务必 URL 编码 query 字符串,不要拼接——QuestDB 不支持 bind 参数,SQL 注入风险由你承担
- 别设超时太短:
http.Client.Timeout至少 30s,复杂窗口聚合可能耗时数秒
关键配置项与易踩坑点
QuestDB 服务端几个配置直接影响 Go 客户端行为,容易被忽略:
-
cairo.max.uncommitted.rows:默认 262144,超过此数未 commit 的行会触发 flush。若 Go 端写入速率不稳,可能引发 ILP 连接被 reset —— 建议调大到 100 万,或确保客户端有稳定 batch size -
http.enabled和pg_wire_enabled必须显式设为true(默认仅 ILP 启用),否则 REST/PG 查询端口不监听 -
line.tcp.splitter.buffer.size:ILP TCP 分包缓冲区,默认 64KB,高吞吐场景建议调至 256KB,避免频繁 flush 导致延迟毛刺 - Go 端 DNS 缓存问题:若用域名连 QuestDB(如
questdb.prod.svc.cluster.local),K8s 中 DNS TTL 短会导致dial tcp: i/o timeout—— 在 client 初始化后加http.DefaultClient.Transport.(*http.Transport).DialContext自定义拨号器,强制缓存 IP
真正难调的从来不是 Go 代码,而是 ILP 流控与 QuestDB 存储引擎 commit 策略之间的节奏匹配——batch size、buffer size、uncommitted rows 三者必须协同,差一个数量级就可能从百万写入跌到十万级。


















