应使用 pgxpool.pool 而非 pgx.connect(),因后者无连接池、不支持复用,高并发下易耗尽端口与连接;pgxpool.pool 提供连接复用、健康检查、超时控制及并发安全,需全局复用、合理配置 maxconns/minconns/idletime/lifetime,并注意 context 传递、rows.close() 和错误处理。

为什么不能直接用 pgx.Connect() 做长连接
因为 pgx.Connect() 返回的是单次使用的 *pgx.Conn,它不带连接池,用完就得手动 conn.Close();高并发下频繁建连/断连会打满 TCP 端口、触发 too many connections 错误,且 TLS 握手开销不可忽视。
真正该用的是连接池:pgxpool.Pool —— 它复用底层连接、支持异步获取、内置健康检查和空闲连接回收。
-
pgxpool.Connect()是首选,返回*pgxpool.Pool,可安全并发调用.Query()/.Exec() - 别把
pgxpool.Pool当局部变量,必须全局复用或注入到 handler 里,否则池子一丢就退化成裸连 - 默认最大连接数是 4,生产环境通常要设为
max_connections * 0.8(比如 PG 的max_connections = 100,这里设pool_max_conns = 80)
如何配置 pgxpool.Config 避免连接卡死或泄漏
硬编码连接字符串或忽略超时,是导致 goroutine 积压的主因。必须显式控制生命周期:
- 设置
MaxConns和MinConns:前者防打爆 DB,后者保冷启动响应(如MinConns: 5) - 必设
MaxConnLifetime(建议 30m)和MaxConnIdleTime(建议 5m),防止被中间件(如 pgbouncer)或云数据库(如 AWS RDS)静默断连后连接卡在idle in transaction -
HealthCheckPeriod设为 30s,让池子主动探测失效连接,避免首次查询才暴露server closed the connection unexpectedly - 连接字符串里加
sslmode=require(不是verify-full,除非你真有证书路径);若用 Unix socket,路径写成host=/var/run/postgresql
pgxpool.Pool 查询时怎么避免 context 超时穿透失败
很多同学只给 ctx 加了 Timeout,但没意识到:如果连接池本身拿不到连接,ctx 超时不会中断等待,而是继续卡在 acquireConn 阶段——直到池子内部 acquireCtx 超时(默认 5s),这和你的业务 ctx 完全无关。
- 务必用
context.WithTimeout(ctx, 2*time.Second)包一层再传给pool.Query(),而不是依赖外部 ctx - 遇到
context deadline exceeded,先查日志里有没有failed to acquire connection from pool;有就是池子满了或连接卡死,不是 SQL 慢 - 批量操作别用
pool.Exec()循环,改用pgx.Batch+pool.SendBatch(),减少 round-trip 和锁竞争 - 事务内慎用
pool.Begin(),它从池子里独占一个连接;高频小事务建议用pool.BeginTx(ctx, pgx.TxOptions{})并尽快tx.Commit()
连接池泄露的两个隐蔽信号
不是所有泄露都报错。当观察到 pg_stat_activity 中 state = idle 的连接数持续高于 pool.MaxConns,或 pgxpool 的 pool.Stat().AcquiredConns 长期 > 0 且不下降,基本可以确认泄露。
- 最常见原因:忘了
rows.Close()(尤其在for rows.Next()后没 defer 或没覆盖所有分支) - 另一个坑:用
pool.QueryRow().Scan()时 panic 了,但没 recover,导致连接没归还;应统一用err := pool.QueryRow(ctx, sql).Scan(&v)并检查 err - 调试技巧:启动时加
log_level: pgx.LogLevelDebug,看日志里是否有acquired conn但没对应released conn
连接池不是黑盒,它的状态可读、行为可控。关键是别把它当“自动续命”的魔法,而要当成需要主动监控和约束的资源。漏掉一次 rows.Close(),可能撑不过一个流量高峰。











