Hyperf事务块内混用Redis或HTTP请求必然导致连接错乱、数据不一致或“MySQL server has gone away”错误,因协程切换使事务绑定的DB连接被其他协程误用;正确做法是所有IO操作必须在事务外完成,事务块仅执行纯SQL操作。

Hyperf 里事务块内混用 Redis 或 HTTP 请求,大概率导致连接错乱、数据不一致或 MySQL server has gone away 错误——这不是偶然,是协程连接复用机制下的必然结果。
事务块里不能直接调用 Redis::get() 或 $http->get()
Hyperf 的数据库连接池和 Redis 连接池都按协程 ID 隔离,但事务一旦开启,DB 连接就被当前协程独占并绑定到事务上下文;此时若在事务中发起 Redis 查询或 HTTP 调用,会触发协程切换,而事务持有的 DB 连接可能被其他协程误用(尤其当连接池 min_connections > 1 时)。
- 现象:事务提交后报
SQLSTATE[HY000]: General error: 2006 MySQL server has gone away,或 Redis 返回上一个协程的缓存值 - 本质:事务未结束前,协程让出控制权 → Swoole 调度器切走 → 其他协程复用同一 DB 连接 → 事务上下文污染
- 正确做法:所有 IO 操作必须在事务外完成,结果通过变量传入事务闭包
DB::transaction() 里只放纯 SQL 操作
事务块应视为“原子性 SQL 执行单元”,仅包含 DB::insert()、DB::update()、模型 save() 等直连 PDO 的操作。任何带协程调度的行为(哪怕只是 Coroutine::sleep(0))都会破坏事务隔离。
- 错误示例:
DB::transaction(function () { $user = User::find(1); Redis::set('user:1', $user->name); // ❌ 触发协程切换 }); - 正确写法:先查 Redis / 调 API / 做计算,再进事务块:
$cacheName = Redis::get('user:1'); DB::transaction(function () use ($cacheName) { User::where('id', 1)->update(['name' => $cacheName]); }); - 注意:事务内调用模型方法时,确保没隐式触发事件监听器(如
eloquent.saved),否则监听器里的 IO 也会越界
需要事务 + 外部服务联动?用两阶段提交模式
比如“扣库存 + 发消息”,不能把消息发送塞进事务,而要拆成“事务内标记状态 + 事务外异步推送”。Hyperf 提供了可靠的补偿机制,比强一致性更稳。
- 第一步:事务内更新库存并写入
message_queue表(状态为pending) - 第二步:事务成功后,立即投递
PushMessageTask异步任务,消费message_queue记录并调用第三方接口 - 第三步:异步任务失败时,靠定时任务扫描超时
pending记录做重试或告警,避免阻塞主流程 - 关键点:绝不允许异步任务回调里再开事务操作原表,否则形成嵌套事务陷阱
最易被忽略的是事务内模型事件和 Accessor 属性访问——它们可能悄悄触发数据库查询或 Redis 调用,表面看没 IO,实则已越界。上线前务必用 Hyperf\Logger\LoggerFactory 打印协程 ID 日志,确认事务块执行期间没有跨协程资源访问。











