Hyperf 中协程 MySQL 事务必须与协程生命周期严格对齐,使用 DB::transaction() 自动管理,禁止跨协程操作、事务内启新协程、复用失败事务;CLI 需启用协程,读写分离时事务须指定写库连接。

Hyperf 中协程 MySQL 事务不是“自动协程化”就万事大吉,关键在事务生命周期必须与协程严格对齐,否则容易连接错乱、事务丢失或死锁。它不依赖传统 PHP 的请求销毁机制,而是靠协程上下文隔离资源。
事务必须在单个协程内闭环完成
Hyperf 的数据库连接和事务状态是协程绑定的,每个协程拥有独立连接和事务上下文。跨协程复用事务、在协程外开启再进协程提交,都会出问题。
- ✅ 正确:用 DB::transaction(fn() => { ... }) —— 框架自动处理 begin/commit/rollback,且全程在当前协程内
- ❌ 错误:手动调用 DB::beginTransaction() 后,在另一个 go() 协程里执行 DB::commit() —— 连接不匹配,大概率报 2006 或静默失败
- ⚠️ 注意:不要在事务闭包里启动新协程去跑 DB 操作(如 co(fn() => User::create(...))),那会脱离当前事务上下文
事务中出错要重试?得新建事务,不能续跑
如果事务因死锁(1205)或连接断开(2006)失败,不能简单循环重试同一事务对象。每次重试都必须开启全新事务,否则残留状态会导致二次失败。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 用 for 循环包裹 DB::transaction() 调用,每次迭代都是干净的新事务
- 捕获 PDOException,只对可重试错误码(如 '1205', '2006', '2013')继续,其余直接抛出
- 加简单退避:co::sleep(0.1 * ($i + 1)),避免瞬间重试压垮数据库
命令行或定时任务里用事务?先启协程环境
CLI 场景默认无协程,DB::transaction() 会退化为同步阻塞。必须显式启用:
- 开头加 Swoole\Runtime::enableCoroutine(true)(Swoole 引擎)或 Swow\Runtime::enableCoroutine()(Swow)
- 再通过 DI 注入 ConnectionInterface 或调用 DB::connection() 获取连接
- 事务仍走 DB::transaction(),框架会自动适配 CLI 下的协程连接池
读写分离场景下事务必须走写库
Hyperf 不自动识别“当前是否在事务中”并切库。如果你配置了 write/read 两个连接池,事务操作必须显式指定写连接:
- ✅ DB::connection('mysql_write')->transaction(...)
- ❌ 用默认连接(可能指向 read 池)开启事务,写操作会失败或被路由到从库
- 强一致性读(如刚写完立刻查)也建议走写库,避免主从延迟导致数据不一致










