连接池泄露是确定性崩溃前兆,需通过waitcount/waitduration快速识别、限制maxopenconns显形、确保defer在获取连接后立即绑定执行,并杜绝跨协程共享连接。

连接池泄露不是“偶发错误”,而是资源持续累积的确定性崩溃前兆——只要没归还,就永远卡住。
查 Stats() 之前先确认你有没有在用对对象
很多人调 db.Stats() 或 pool.Stats() 发现 InUse 持续上涨,但根本没意识到自己压根没在用那个连接池实例。常见陷阱包括:
- 每次 HTTP 请求都 new 一个
http.Client,而不是复用全局单例 —— 这样每个 Client 都自带独立 Transport 和连接池,Stats()查的是你没保存的那个 - Redis 场景下用了
redis.NewClient()却没设PoolSize,实际走的是默认 pool(可能只有 10 个),但你以为自己配了 50 - 数据库里开了多个
*sql.DB实例(比如按库分 db1/db2),却只监控了其中一个的Stats()
看 WaitCount 和 WaitDuration 是最快暴露泄漏的窗口
这两个指标比 InUse 更敏感:只要出现非零值,说明已有协程在排队等连接 —— 这是连接被长期占用或未归还的铁证。
-
WaitCount > 0且持续增长 → 不是连接数不够,是连接“有去无回” -
WaitDuration超过 100ms 就该警觉;超过 1s 基本可判定存在泄漏 - 注意:
MaxOpenConns设得过大(比如 1000)会掩盖问题,建议先临时调成 5~10,让泄漏快速显形
defer 归还必须写在 get 之后、任何 return 之前
这是 Swoole 协程和 Go 里最常翻车的位置 —— defer 看似自动,实则极易被逻辑分支绕过。
- 错例:
if err != nil { return }写在defer之后 → 错误路径下defer根本不执行 - 错例:
defer pool.Put(conn)写在函数开头 →conn = pool.Get()失败时 panic,conn是 nil,Put(nil)直接 panic - 正确写法:获取成功后立刻
defer,且绑定具体变量:conn := pool.Get(); defer func() { pool.Put(conn) }()
别信 __destruct,也别传 conn 到 goroutine 里
Go 没有析构函数语义,Connection 类型里的 Close() 或 __destruct 在协程退出时不会被调用;更危险的是跨协程传递连接实例。
- 主协程
conn := pool.Get()后直接传给go doSomething(conn)→ 子协程操作时主协程可能已Put()或销毁conn,底层 socket 状态错乱 - 即使子协程只读,也存在并发读写底层 net.Conn 的风险,TCP 连接可能被提前关闭或 reset
- 真正安全的做法:每个
go协程都自己pool.Get(),自己defer pool.Put(),不共享连接对象
最麻烦的不是找不到泄漏点,而是泄漏发生在第三方库启动的后台 goroutine 里 —— 比如某个 SDK 初始化时悄悄起了一个心跳协程,但它用的连接没走你的 pool,也不受你 defer 控制。这种得靠 pprof/goroutine 对比 + 手动 grep 库源码里的 amqp.Dial、redis.Dial、http.Transport 调用点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











