Workerman协程中事务回滚失败主因是跨协程操作、连接不一致、表引擎非InnoDB、异常被吞或连接状态残留;须确保事务全程同协程、同连接、同引擎,并显式抛出异常及清理连接状态。

Workerman协程中数据库事务回滚失败,往往导致订单状态更新成功但退款记录未写入、库存扣减后无法还原等数据不一致问题,这类故障在高并发退款、支付回调场景下尤为致命。
确认是否在协程上下文内开启事务
第一步:检查事务代码是否包裹在 go 或 Co::create 内部。若事务开启(Db::beginTransaction())在主协程,而 commit() 或 rollback() 在 go 协程中执行,两者使用的 MySQL 连接句柄必然不同——【事务操作必须全程在同一个协程内完成】。
第二步:用 var_dump(get_current_coroutine_id()) 分别打印开启事务与提交/回滚前的协程 ID,ID 不一致即为跨协程操作,回滚必然失效。
第三步:改用 Db::transaction() 闭包方式重写逻辑,该方法自动绑定当前协程连接,避免手动管理生命周期。
排查模型是否指定了独立连接
方法一:打开模型类,查找 protected $connection = 'xxx' 定义。若存在,说明该模型强制使用指定连接池,而 Db::transaction() 默认操作的是主连接(default),二者物理连接不同,事务自然隔离。
方法二:在事务闭包内显式传入连接名,例如 Db::connection('mysql')->transaction(function () use ($order) { ... }),确保模型操作与事务使用同一连接实例。
注意:ThinkORM 中模型指定连接后,Db::transaction() 必须同步指定相同连接名,否则事务对模型操作完全无效。
验证表引擎是否支持事务
执行 SQL:SHOW CREATE TABLE `your_table_name`;,检查输出中 ENGINE=InnoDB 是否存在。若为 MyISAM 或 Memory,【MySQL 会静默忽略 START TRANSACTION 和 ROLLBACK 指令】,所有 DML 操作立即生效且不可逆。
这一步必须查,尤其当表是历史迁移或从其他系统导入时,InnoDB 并非 MySQL 默认引擎(5.5 之前默认 MyISAM)。
检查异常是否被吞掉
事务闭包内若发生 PHP Notice 或 Warning(如数组索引不存在、变量未定义),而代码又用了 @ 抑制符或全局错误处理器未抛出 Throwable,catch 块根本不会触发,rollback() 就永远不会执行。
临时删掉所有 @ 符号,在 php.ini 中设置 error_reporting = E_ALL 并开启 display_errors = On,让错误直接暴露出来。
务必保证事务闭包内任何异常都最终向上抛出,否则框架无法感知失败并自动回滚。
确认连接是否被复用污染
Workerman 常驻内存特性下,若某次请求的事务未正常结束(如进程被 kill、超时中断),该连接上的事务状态可能残留。下次复用此连接时,START TRANSACTION 实际并未真正开启新事务,后续 ROLLBACK 只是回滚一个“不存在的事务”。
解决方案:在 onWorkerStart 中初始化数据库连接后,立即执行 $db->query('SET SESSION autocommit = 1'),强制每次连接初始为自动提交模式;事务内再手动 SET autocommit = 0。
更稳妥做法是在事务闭包末尾加兜底检测:if ($db->inTransaction()) { $db->rollback(); },防止连接状态残留。

















