mysql死锁是innodb主动检测并回滚事务的数据库层机制,而swoole“死锁”是协程调度器失活导致进程静默挂起;前者源于事务循环等待资源,后者因单协程sleep且无i/o唤醒事件。

MySQL死锁是数据库层检测,Swoole死锁是协程调度层挂起
MySQL死锁由InnoDB存储引擎主动检测并干预:当两个事务形成循环等待(如A锁行1等行2、B锁行2等行1),InnoDB会在毫秒级发现并回滚代价更小的事务,抛出Deadlock found when trying to get lock错误。这不是PHP或Swoole的问题,而是数据库自己做的决策。
Swoole里说的“deadlock”不是数据库锁冲突,而是协程调度器失活后打印的提示:[fatal error]: all coroutines (count: 1) are asleep - deadlock!。它不涉及任何锁资源竞争,只说明当前进程里只剩一个协程,且它正在调用Swoole\Coroutine\System::sleep()、co::read()等让出控制权的操作,而调度器找不到其他协程或I/O事件可轮转——于是整个进程卡住,CPU归零,无报错、无超时。
两者触发条件和排查手段完全不同
MySQL死锁常见于并发UPDATE/SELECT FOR UPDATE场景,尤其事务中加锁顺序不一致时。查SHOW ENGINE INNODB STATUS能直接看到最近一次死锁详情,包括事务ID、SQL、锁类型、等待关系。
Swoole协程死锁典型出现在这些地方:
- 父进程禁用协程(
swoole_async_set(['enable_coroutine' => false])),子进程却启动了协程并只调用sleep(1) - 定时器回调(
Swoole\Timer::after())里启协程,但协程内无I/O唤醒点(比如没co::gethostbyname()、没co::read()、没HTTP请求) - 协程池里每个协程都只做
sleep,没有实际任务分发逻辑
验证方式也不同:MySQL看日志和状态;Swoole用strace -p [pid] -e trace=futex,clone,wait4看是否卡在futex系统调用,再配合php ./vendor/bin/swoole-tracker status检查coroutine_num是否长期为0或1。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
别把WorkerError里的$signal === 11当成死锁
Hyperf或原生Swoole服务异常退出时,很多人看到WorkerError回调就以为是死锁,其实$signal === 11代表SIGSEGV(段错误),属于C层崩溃,必须开core dump用gdb分析;$signal === 0 && $exitCode === 255才是PHP致命错误,靠register_shutdown_function兜底——但它必须在onWorkerStart里注册,全局写一次无效。
真正协程调度死锁不会触发WorkerError,它安静得像进程睡着了:无日志、无CPU占用、kill -USR2也无响应。这时候翻代码比看错误码管用:搜go(和sleep(组合,重点检查它们是否出现在swoole_async_set(['enable_coroutine' => false])之后的上下文里。
修复路径不能混用
MySQL死锁靠改SQL顺序、加索引、设超时、用乐观锁解决;Swoole协程死锁只能从调度环境入手:
- ✅ 推荐:统一启用协程,
swoole_async_set(['enable_coroutine' => true])放在Process创建前,确保定时器、子进程、协程都在同一调度器下 - ⚠️ 慎用:子进程开头就
swoole_async_set(['enable_coroutine' => false]),然后用同步sleep(1)——放弃并发能力,仅适合守护脚本调试 - ❌ 别试:只加
go()不确认调度器在线,协程根本跑不起来,等于白写
最容易被忽略的是:Swoole的“死锁”不是错误,是调度器主动挂起。它不抛异常、不占CPU、不写日志,只有当你盯着coroutine_num和strace输出时,才能确认它是不是真睡过去了。










