协程结束时连接未归还是因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增减计数器,比依赖池自身统计更可靠 - 加个定时任务每 5 秒输出一次
atomic->get()值,突增就是泄露信号 - 注意:不要在协程里直接操作全局
swoole_table计数器而不加锁——虽然Atomic是线程安全的,但多个协程同时get后都去put,仍可能因异常跳过导致计数失准,所以务必和defer绑定
真正难排查的,是那些只在高并发压测下才出现的“偶发归还不及时”——比如某个 try/catch 里吞了异常却忘了 defer,或者 yield 后协程被调度走,连接对象被 GC 掉但没触发归还。这种得靠原子计数 + 定时采样双保险,光盯日志或池统计容易漏。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/shoujipingce/126719.html