用 pgx/v5 替代已归档的 lib/pq;连接字符串禁用 sslmode=disable,上线必用 require;敏感信息从环境变量读;sql.ErrNoRows 需显式判断;批量插入用 pgx.Batch 而非循环 Exec;事务必须全程使用 *pgx.Tx;调大 MaxConns 并设合理 health_check_period。

用 database/sql + pgx 连 PostgreSQL 最稳
别碰 lib/pq,它已归档,不维护了;pgx 是当前 Go 生态事实标准,原生支持连接池、批量操作、类型映射更准。直接上 pgx/v5(不是 pgx/v4),它对 context 取消超时、取消查询更可靠。
-
pgx.ConnectConfig比pgx.Connect更可控:能显式设ConnConfig.RuntimeParams(比如timezone)、MaxConns、HealthCheckPeriod - DSN 里别写
sslmode=disable,本地开发可接受,但上线必须用sslmode=require或更严模式;PostgreSQL 12+ 默认拒绝非 SSL 连接 - 连接字符串中的
user和password建议从环境变量读,别硬编码——os.Getenv("DB_USER")比拼接 DSN 安全得多
sql.ErrNoRows 不是错误,是流程分支
查单行用 QueryRow().Scan() 时,sql.ErrNoRows 表示“没找到”,不是连接失败或 SQL 写错。很多人一见 error 就 panic 或 log.Fatal,结果线上查不到用户就挂了。
- 必须显式判断:
if errors.Is(err, sql.ErrNoRows) { /* 处理空结果 */ },不能只写if err != nil -
pgx的QueryRow().Scan()行为和database/sql一致,但它的QueryRow().Err()要手动调,别漏掉 - 如果业务逻辑允许“不存在即默认值”,直接用
coalesce在 SQL 层兜底,比 Go 层 if-else 更快
批量插入别用 for 循环 Exec,用 pgx.Batch
循环 100 次 db.Exec 插 100 行,实际发 100 个网络包,延迟爆炸;PostgreSQL 本身支持一次传多行,pgx.Batch 就是干这个的。
- 构造
pgx.Batch后,用pgxpool.Pool.SendBatch提交,不是Exec;返回的*pgx.BatchResults要.Close(),否则连接泄漏 - 单次 Batch 不要超过 1000 行——太大易触发 PostgreSQL 的
max_stack_depth或内存压力,太小又没收益 - 如果字段含
NULL,确保 struct 字段用指针或sql.NullString类型,否则Scan会报cannot scan into *string from NULL
事务里别混用 *pgx.Conn 和 *pgxpool.Pool
开事务必须用 pool.Begin() 拿到 *pgx.Tx,然后所有操作走这个 tx 对象。有人图省事,在事务里还拿 pool.Query,结果查询跑在别的连接上,根本不在事务里。
立即学习“go语言免费学习笔记(深入)”;
-
tx.Query/tx.QueryRow/tx.Prepare才属于当前事务;pool.Query是新连接,自动提交,事务隔离失效 - 事务超时要靠
context.WithTimeout传给pool.Begin,不是靠 SQL 的SET statement_timeout - 别忘了
defer tx.Rollback(),并在成功后显式tx.Commit()——pgx.Tx不会自动 commit
最常被忽略的是连接池配置:默认 MaxConns: 4,高并发下排队等连接比 SQL 慢得多;还有 health_check_period 设太长,坏连接卡在池里半天才被踢掉。


















