swoole中mysql查询返回空或报hy000错误,主因是协程上下文混用非协程安全资源,如在onreceive中new pdo或复用全局pdo单例,导致协程切换时连接句柄被覆盖或关闭;必须使用swoole\coroutine\mysql并在协程内创建、使用、关闭连接,禁跨协程传递连接对象。

京东 PHP 二面问 Swoole,核心不是考你背 API,而是看你有没有在线上真实压过、调过、修过 —— 尤其是协程上下文丢失、MySQL 连接复用异常、定时器内存泄漏这三类问题,90% 的候选人栽在第二点。
为什么 Swoole\Coroutine\MySQL 查询偶尔返回空或报 SQLSTATE[HY000]: General error
这不是 MySQL 服务端问题,大概率是协程上下文里混用了非协程安全的资源。比如你在 onReceive 回调里直接 new 了一个 PDO 实例,或者复用了全局单例的 PDO 连接 —— 协程切换时句柄被其他协程覆盖或关闭了。
- 必须用
Swoole\Coroutine\MySQL或co::mysql(),不能混用PDO/mysqli - 连接必须在协程内创建、使用、关闭;不要跨协程传递连接对象(哪怕只是变量引用)
- 如果用连接池,确认池子底层封装的是
Swoole\Coroutine\MySQL,不是简单包装 PDO - 开启
swoole.display_errors = On和swoole.log_level = 5,错误日志里会明确标出 “connection closed by coroutine switch” 类提示
定时器 tick / after 在 reload 后不触发?
reload 时主进程会 kill 掉所有 worker 进程,但协程内的定时器不会自动销毁,旧定时器可能还在运行,而新进程没注册 —— 表现就是“看起来没触发”,其实是新老定时器错位。
- 所有定时器必须在
onWorkerStart回调里注册,不能写在全局作用域 - 避免在
onReceive或 HTTP 回调里反复调用Swoole\Timer::tick(),每次都会新建一个 timer_id,reload 后残留 ID 不会被清理 - 需要动态控制时,用
Swoole\Timer::clear()配合唯一标识管理,比如$timerId = Swoole\Timer::tick(1000, fn() => {...});,并在onWorkerStop里主动Swoole\Timer::clear($timerId) - 注意:PHP-FPM 没有
onWorkerStart,这类逻辑在 Swoole 下才生效
go(function () { ... }) 里调用 curl_exec 为什么卡死?
curl_exec 是同步阻塞函数,放进协程里也不会自动变成协程版 —— 它会把整个协程挂起,等 cURL 返回,期间无法调度其他协程,表现就是 CPU 低、QPS 断崖下跌、strace 显示卡在 recvfrom。
- 必须改用
Swoole\Coroutine\Http\Client或co::httpGet()等原生协程客户端 - 如果依赖第三方 SDK(比如腾讯云 COS SDK),检查它是否支持
co模式;不支持就只能封装成defer+exec子进程,但性能损失大 - 临时调试可用
ini_set('swoole.enable_coroutine', '0')关闭协程,验证是否为该问题,但上线前必须改掉 - 注意
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 3000)在协程下仍有效,但超时后协程不会自动 resume,要靠Swoole\Coroutine::sleep()或信号机制配合处理
真正难的不是写出让 Swoole 跑起来的代码,而是当 top 显示 CPU 20%、netstat 看到 ESTABLISHED 连接数缓慢上涨、memory_get_usage() 每小时涨 2MB 时,你能立刻想到去查 Swoole\Timer::list() 和 Swoole\Coroutine::stats() —— 这些命令不常写,但线上翻车时,它们比任何文档都管用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











