协程结束时连接未归还是因defer位置错误或缺失;每个协程须独立获取/归还连接;需调小maxIdleTime/maxWaitTime快速暴露问题;用Atomic绑定defer精准统计连接数。
defer 没写对是最常见原因协程退出前未显式调用 pool->put($conn) 或未用 defer 保证归还,连接就卡在 pool->list 里不动了。这不是“自动回收”,Swoole 不会替你管连接生命周期。
实操建议:
defer 必须写在获取连接之后、业务逻辑开始之前,且不能被 return 或异常跳过(比如写在 if 分支里就可能漏掉)__destruct:协程销毁不触发对象析构,Connection 类里的 __destruct 几乎无效$conn = $pool->get();defer(function () use ($pool, $conn) { $pool->put($conn);});// 后续业务逻辑...
go 启动的协程里复用主协程的连接对象会直接泄露连接对象不是线程安全的,更不是协程间共享安全的。把主协程拿到的 $conn 直接传进 go 匿名函数,在子协程里操作它,主协程可能已归还或销毁该连接,子协程再调用 $conn->query() 就会出错,且连接无法被正确归还。
实操建议:
go 协程必须独立调用 $pool->get() 获取新连接,用完各自归还$conn 实例,哪怕只是只读也不行——连接底层 socket 可能被并发读写破坏状态maxIdleTime 和 maxWaitTime 设太大会掩盖泄露问题默认 maxIdleTime=60(秒),意味着空闲连接最多活一分钟才被清理;maxWaitTime=0(阻塞等待)会让协程卡住而不是报错。这两项设得宽松,会让连接“看起来没泄露”,其实只是延迟暴露。
实操建议:
maxIdleTime 改成 5,快速验证连接是否及时释放maxWaitTime 设为 0.1(100ms),一旦拿不到连接立刻报错,比死等更容易定位卡点maxIdleTime(如 30),避免连接长期滞留占用资源swoole_table 或 Atomic 手动统计连接数,比看日志更准日志里打印 get/put 很容易漏掉异常分支,而 $pool->stats() 返回的 usedCount 和 idleCount 是实时快照,但要注意它只反映当前池内状态,不包含已获取但未归还的“悬挂连接”。
实操建议:
get 和 put 处分别用 Atomic 增减计数器,比依赖池自身统计更可靠atomic->get() 值,突增就是泄露信号swoole_table 计数器而不加锁——虽然 Atomic 是线程安全的,但多个协程同时 get 后都去 put,仍可能因异常跳过导致计数失准,所以务必和 defer 绑定try/catch 里吞了异常却忘了 defer,或者 yield 后协程被调度走,连接对象被 GC 掉但没触发归还。这种得靠原子计数 + 定时采样双保险,光盯日志或池统计容易漏。