Hyperf协程MySQL“阻塞住”的核心原因是协程未按预期让出控制权:一是CPU密集型任务无IO触发调度,导致线程被独占;二是连接池耗尽引发协程无限等待;三是MySQL服务端未协同超时(如缺少MAX_EXECUTION_TIME hint或read_timeout未显式配置);四是连接被wait_timeout等机制提前关闭而心跳未及时探活。

Hyperf 协程 MySQL 出现“阻塞住”,通常不是 MySQL 本身卡死,而是协程调度被意外中断或资源不可用,导致请求挂起、响应延迟甚至超时。核心原因在于:协程本该在 IO 阻塞时自动让出控制权,但某些场景下它既没完成 IO,也没切换出去,反而一直占着线程不放。
一、CPU 密集型任务导致协程无法调度
协程调度依赖“遇到 IO 就切换”。如果代码里全是循环计算、JSON 解析、加密哈希等纯 CPU 操作,没有 sleep、DB::select()、Redis::get() 这类协程友好的 IO 调用,调度器就无从介入。
结果是:单个协程霸占线程,其他协程排队等待,接口变慢、CPU 拉满、新请求进不来。
- 避免在协程中写长循环(如
for ($i = 0; $i ) - 必须做大量计算时,插入
co::sleep(0.001)或轻量 IO(如Redis::ping())触发切换 - 极端情况可考虑用
Swoole\Process启动独立线程处理
二、连接池耗尽,协程无限等待空闲连接
报错常见为 WaitTimeoutException 或日志显示“waiting for connection”。这不是 MySQL 慢,而是连接都被占用了,新协程只能排队。
- 查当前连接数:
ss -s | grep ESTAB或swoole_get_local_socket_count() - 检查
config/autoload/databases.php中pool.max_connections是否合理(别超过 MySQL 的max_connections) - 确保事务、pipeline、Redis multi 等操作都正确
exec()或finally中释放 - 启用
heartbeat => 30和max_idle_time => 60,避免连接“假活”
三、MySQL 服务端未配合协程超时机制
协程客户端设了 read_timeout => 5.0,但若 MySQL 服务端正在执行一个慢查询(比如没索引的 SELECT FOR UPDATE),它不会主动中断——协程会一直等下去。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
DB::timeout(5)只对 Query Builder 生效,对原生DB::select()或Swoole\Coroutine\MySQL无效 - 真正生效的是:
- SQL 中加
/*+ MAX_EXECUTION_TIME(5000) */(仅 SELECT,且需 PROCESS 权限) - MySQL 侧设置
max_execution_time = 5000(会话级) - 客户端配置
read_timeout+write_timeout(必须显式写在options里)
- SQL 中加
四、连接被中间件或数据库提前关闭
比如 MySQL 的 wait_timeout = 300(5 分钟),而连接池 max_idle_time 设成 600,空闲连接就会被 DB 主动断开。下次协程取到这个连接执行 SQL,直接报 Connection has been closed 或 server has gone away。
- 连接池
heartbeat必须开启(建议30秒),定期发 PING 探活 -
max_idle_time应略小于 MySQL 的wait_timeout(如 DB 是 300,池设 240) - 不要调小连接池的
wait_timeout参数(那是协程排队时间,不是连接空闲时间)
不复杂但容易忽略

















