Hyperf 3.1 事务必须在单个协程内原子完成,根本原因是数据库连接按协程 ID 隔离,DB::transaction() 封装了连接获取、事务开启、执行、提交/回滚及归还的完整闭环,而手动 beginTransaction/commit 或跨协程操作会导致连接错用、事务丢失或 MySQL server has gone away。

Hyperf 3.1 的事务不能跨协程执行,根本原因在于数据库连接是**按协程 ID 隔离的**,且底层 MySQL 连接在协程切换时无法安全共享。
协程连接池的隔离机制
Hyperf 使用 Swoole 协程 MySQL 客户端,每个协程从连接池获取专属连接,并通过 Context::set() 绑定到当前协程上下文。一旦协程结束或切换,该连接自动归还池中,其他协程无法复用其事务状态。
- 调用
DB::transaction()时,框架会为当前协程申请连接、开启事务、执行闭包、最后 commit/rollback —— 整个流程严格绑定在单个协程生命周期内 - 若手动调用
beginTransaction()后切到另一个协程再调commit(),实际操作的是两个不同连接,MySQL 会报错SAVEPOINT does not exist或静默失败 - 错误示例:在协程 A 中 start transaction,然后用
go启动协程 B 执行 commit → B 拿不到 A 的连接,事务已丢失
为什么 DB::transaction() 是唯一安全方式
它封装了完整的“获取连接 → 开启事务 → 执行逻辑 → 异常回滚/成功提交 → 归还连接”闭环,全程不脱离当前协程上下文。
- 不推荐手写
beginTransaction/commit,容易遗漏异常捕获或连接归还 - 嵌套事务在 Hyperf 中不被支持(MySQL 本身也不支持真正的嵌套事务),
DB::transaction()内部仅用 savepoint 模拟,仍需在同一协程内完成 - 跨协程场景(如投递 task)必须把事务相关数据提前查好、序列化传入,task 内重新建连接做独立操作
常见误用与后果
直接跨协程操作事务连接,轻则事务失效(数据未回滚)、重则引发连接污染,导致后续请求出现 MySQL server has gone away。
- 在 TaskWorker 中尝试复用 Worker 协程的 DB 实例 → 报
Entry not found或空结果 - 在定时任务或异步 job 中未用
DB::transaction()包裹,而是分段调用 → 事务不生效,数据不一致 - 使用
go并发多个 DB 操作却共用一个 connection 变量 → 协程间互相覆盖状态,产生不可预测行为
事务必须在单个协程内原子完成,这是协程模型下保障数据一致性的基本约束。











