如何在Golang中操作PostgreSQL数据库 Go语言pgx库高性能实践

3次阅读

用 pgxpool.connect 配连接池,设 maxconns 控制大小,程序退出前调 close();查单行用 queryrow 并 scan,多行用 query 并 close;批量用 pgx.batch,建表优先 timestamptz。

如何在Golang中操作PostgreSQL数据库 Go语言pgx库高性能实践

pgx 连接池怎么配才不泄漏内存

pgxpool.Connect 而不是 pgx.Connect,后者只建单连接,没池、不复用、一请求一连就崩。连接池必须显式关闭,否则 goroutine 和 socket 会一直挂着。

  • pgxpool.Connect 返回的 *pgxpool.Pool 需在程序退出前调用 Close(),比如 defer pool.Close() 或在 main 结束时关
  • 连接池大小别硬写死,用 pgxpool.Config.MaxConns 控制上限,小服务设 10~20 足够,大并发pg_stat_activity 实时观察
  • 环境变量里传 DSN 时,记得加 sslmode=disable(本地开发)或 sslmode=require(生产),漏了会卡住连接、超时后报 dial tcp: i/o timeout

Query 和 QueryRow 该选哪个

查单行且确定有结果,用 QueryRow;查多行、不确定条数、要遍历,用 Query。错用会导致 panic 或漏数据——QueryRow 遇到零行直接返回 sql.ErrNoRows,而 Query 遇到零行只是返回空结果集,不会报错。

  • QueryRow 后必须调 Scan(),否则连接不会归还池里,积压后触发 context deadline exceeded
  • Query 返回 *pgx.Rows,用完必须 rows.Close(),不然连接一直被占着,哪怕用 for rows.Next() 遍历完了也不自动关
  • 想查一行但字段可能为空?别用 QueryRow.Scan(&v) 直接扫,改用 Scan(&sql.NULLString{})Struct tag 加 pgtype:"text" 配合自定义类型

批量插入为什么比 for 循环快十倍

因为 pgx.Batch 把多条语句打包成一个网络往返,避免 TCP 握手和 postgresql 解析开销。普通 for 循环每 insert 一次都走完整 round-trip,QPS 直接掉到 1/10。

  • pool.Begin() 开事务 + batch := &pgx.Batch{} 累积语句 + br := pool.SendBatch(ctx, batch) 提交,比 pool.Exec 单条快得多
  • batch 大小建议控制在 100~1000 条之间,太大容易 OOM 或触发 PostgreSQL 的 max_stack_depth 限制
  • 注意 batch 不支持 RETURNING,要拿插入后的 ID?得切回单条 QueryRow 或改用 INSERT ... select UNNEST(...) 方式

time.Time 存进 PostgreSQL 总是差 8 小时

根本原因是 go 默认用本地时区解析 time.Time,而 PostgreSQL 的 timestamp without time zone 按 UTC 存,timestamp with time zone 才存时区信息。两边时区没对齐,就偏移。

立即学习go语言免费学习笔记(深入)”;

  • 建表时优先用 timestamptz(即 timestamp with time zone),Go 侧 time.Time 值自带时区信息,pgx 能自动转换
  • 如果必须用 timestamp(无时区),写入前统一转成 UTC:t.UTC(),读出来再按需转本地时区
  • 别依赖 time.Localdocker 容器或 linux 服务器上它可能是 UTC,本地 macos 可能是 CST,行为不一致

最麻烦的其实是 NULL 时间字段和零值时间混在一起——Go 的 time.Time{} 是 0001-01-01,PostgreSQL 当成非法时间报 invalid input syntax for type timestamp。得用 *time.Timepgtype.Timestamptz 显式处理空值。

text=ZqhQzanResources