TP6事务回滚成功需满足三条件:未捕获异常、复用同一PDO连接、表引擎为InnoDB;推荐统一Db::transaction闭包操作,异常自动回滚;局部回滚用SAVEPOINT;手动控制须严格配对startTrans/commit/rollback。

TP6 接口事务处理回滚,核心在于确保异常能穿透到事务框架层,并让所有操作共享同一数据库连接、使用 InnoDB 引擎。只要任一环节断开这个链条,rollback 就会静默失效,表面成功,实际数据不一致。
事务自动回滚的前提条件
Db::transaction() 只对未被捕获的异常自动触发回滚。这意味着:
- 闭包内不能有
try-catch吞掉异常却不重抛;PHP 8.0+ 还需捕获\Throwable,否则 fatal error 类错误不会回滚 - 所有 SQL 操作必须复用同一个 PDO 实例:禁用
db('table')、Db::connect()、Db::master()等显式切换连接的操作 - 涉及的表引擎必须是 InnoDB;MyISAM 完全无视事务指令,无论怎么写
rollback都无效
推荐写法:统一事务闭包
把全部关联操作放在一个 Db::transaction() 闭包中,是最安全、最清晰的方式:
return Db::transaction(function () use ($userId, $amount) {
// 扣用户余额
Db::name('user')->where('id', $userId)->dec('balance', $amount);
// 创建订单
$orderId = Db::name('order')->insertGetId([
'user_id' => $userId,
'amount' => $amount,
'status' => 'paid'
]);
// 扣库存(关联商品表)
Db::name('product')->where('id', 1001)->dec('stock', 1);
return $orderId;
});
任何一步抛出异常,整个闭包退出,框架自动 rollback,无需手动干预。
需要局部回滚?用 SAVEPOINT
当主流程必须成功,但某些辅助操作(如发券、记日志、通知)失败可容忍时,可用原生 SAVEPOINT 模拟局部回滚:
- 先执行主逻辑(如更新订单状态)
- 执行
Db::execute('SAVEPOINT sp_coupon'); - 尝试发券,失败则
Db::execute('ROLLBACK TO SAVEPOINT sp_coupon'); - 最后统一
Db::commit()或外层异常时Db::rollback()
注意:MySQL 8.0+ 原生支持,5.7 及以下需改用业务降级方案(比如记录失败但不回滚主操作,靠后续补偿或人工介入)。
手动事务控制的正确结构
若需更精细控制(如跨模型、含复杂判断),应使用 Db::startTrans() + 显式 commit/rollback,但必须严格配对:
Db::startTrans();
try {
Db::name('user')->update(['score' => ['inc', 10]]);
Db::name('log')->insert(['action' => 'score_add']);
Db::commit();
return ['code' => 0, 'msg' => 'success'];
} catch (\Throwable $e) {
Db::rollback();
throw $e; // 或返回错误响应
}
切忌在 try 内吞异常后不 re-throw,也禁止在 catch 中只 log 而不 rollback —— 这会导致事务被默认提交。

















