pgxpool 取消 Acquire 后不断重连,销毁计数却没涨
短请求在空闲连接健康检查期间取消,会把连接带入销毁路径。核对 NewConnsCount 与 ping 时序,必要时给健康检查独立但有界的等待预算。
请求取消后,pgxpool 的 NewConnsCount 持续增加,MaxLifetimeDestroyCount 和 MaxIdleDestroyCount 却没有相应增长。若服务会并行查询、抢先返回后取消其他分支,检查取消是否落在 Acquire 的空闲连接 ping 期间。
pgx 5.11.0 默认会检查空闲超过约一秒的连接,ping 沿用调用方 context。取消使 ping 失败后,池会销毁该连接,再尝试取得资源。这类销毁不属于最大寿命或最大空闲时间计数,不能从两个计数为零推断没有连接被淘汰。
先区分健康检查取消与真实坏连接
保留池大小、空闲量、新建计数、Acquire 取消量与网络往返的时序。连接 PID 变化可作为受控对照,但不是生产日志里需要逐条输出的业务字段。数据库主动断开、寿命到期和查询取消也会造成重连,要分别排除。
不要把 ShouldPing 统一设成 false。那会跳过原有健康检查,不能证明被交给业务的连接可用;增加池上限也只可能把建连压力移到数据库。
无其他 hook 时,可以局部安排有界检查
下面展示 5.11 接口上的配套设置,cfg 是尚未创建池的 pgxpool.Config。它仅适合确认没有其他 ShouldPing 或 PrepareConn 规则的配置入口:
cfg.ShouldPing = func(ctx context.Context, p pgxpool.ShouldPingParams) bool {
if ctx.Err() != nil || p.IdleDuration <= time.Second {
return false
}
pingCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 2*time.Second)
defer cancel()
return p.Conn.Ping(pingCtx) != nil
}
cfg.PrepareConn = func(ctx context.Context, conn *pgx.Conn) (bool, error) {
return true, ctx.Err()
}
需导入 context、time、pgx 及 pgxpool。两秒只是示例预算,按真实网络与并发资源要求选择。健康检查成功时返回 false,避免池再按已取消 context 重复 ping;请求在检查期间离开,则由 PrepareConn 返回 true 与取消错误,让资源归还池、调用方仍失败。
独立检查失败时返回 true,池仍会走正常 ping 与销毁路径,不把已损坏的连接当作健康连接保留。已取消请求在检查前就结束时,也不启动新的 ping。
已有 hook 可能负责租户、角色、会话状态或其他安全检查,不能直接用这段覆盖。应在原规则基础上设计组合次序,再确认 true/false 与 error 四种组合的池行为。context.WithoutCancel 只用于这个有期限的检查,实际业务查询继续使用原请求 context。
为取消延迟与建连成本一起验收
脱离请求取消的健康检查会最多继续到自己的预算,不能描述成请求取消立即释放全部资源。比较受控取消期间的连接复用、真正坏连接淘汰、Acquire 返回错误以及正常查询结果,同时检查峰值持有量。
保留原池配置,通过既有池更替流程切换,不直接修改在用池内部字段。若需要撤回,恢复原 hook 并排空替换相关池;此调整只减少特定检查取消造成的重连,不保证所有取消都不关闭连接,也不改变事务结果核实要求。


