TP5自定义函数中事务必须显式判断SQL执行结果而非仅依赖try-catch,因Db操作成功返回ID或影响行数、失败返回false但不抛异常,只有PDO底层错误才触发catch。

TP5 自定义函数里写事务,核心就一条:不能只靠 try-catch 捕获异常来决定回滚,必须显式检查每条 SQL 的执行结果。
为什么 try/catch 不能直接包住所有 Db 操作?
TP5 的 Db::insert()、Db::update()、Db::delete() 等方法在语法正确但影响行为为 0(比如更新不存在的记录、删除空条件)时,返回 false,但不会抛出异常。这意味着:
-
try块里哪怕有Db::update(...)->where('id', 9999)->update(...)返回false,程序仍会继续往下走,最终执行Db::commit() - 只有 SQL 语法错误、字段不存在、表不存在等底层 PDO 异常才会触发
catch - 所以「业务逻辑失败」和「SQL 异常」是两回事,不能混为一谈
自定义函数中手动控制事务的正确写法
在自定义函数(比如封装成 doOrderTransaction())中,推荐用「显式判断 + 手动 commit/rollback」组合:
function doOrderTransaction($orderData, $payData) {
\think\Db::startTrans();
try {
$orderRes = \think\Db::name('order')->insert($orderData);
$payRes = \think\Db::name('pay_log')->insert($payData);
// 关键:两个都必须成功(返回 > 0 或 true)
if ($orderRes && $payRes) {
\think\Db::commit();
return ['code' => 1, 'msg' => '下单成功'];
} else {
\think\Db::rollback();
return ['code' => 0, 'msg' => '数据写入不完整'];
}
} catch (\Exception $e) {
\think\Db::rollback();
return ['code' => 0, 'msg' => $e->getMessage()];
}
}
注意点:
- 不要依赖
$res > 0判断插入/更新,因为insert()成功返回自增 ID(数字),update()成功返回影响行数(可能为 0 或正整数),统一用if ($res)更稳妥 - 如果某步需要获取 ID(如先插订单再插关联明细),记得用
\think\Db::getLastInsID(),它只对上一次insert()有效,且必须在commit()前调用 - 避免在事务块内调用
$this->success()或其他可能抛出非数据库异常的方法,否则会误触发 rollback
用 Db::transaction() 闭包方式更简洁,但有局限
TP5.1+ 支持自动管理的闭包事务:Db::transaction(function () { ... });。它只在闭包内抛出异常时自动 rollback,所以你得主动 throw:
function doOrderWithClosure($orderData, $payData) {
return \think\Db::transaction(function () use ($orderData, $payData) {
$orderRes = \think\Db::name('order')->insert($orderData);
$payRes = \think\Db::name('pay_log')->insert($payData);
if (!$orderRes || !$payRes) {
throw new \Exception('订单或支付日志写入失败');
}
return ['code' => 1, 'msg' => '成功'];
});
}
优点是不用手动 startTrans/commit/rollback;缺点是:
- 无法捕获并处理不同失败原因(比如区分订单失败 or 支付日志失败)
- 闭包内不能使用
$this(无控制器上下文),不适合复杂业务逻辑 - 嵌套事务不被支持,二次调用
Db::transaction()会报错
真正容易被忽略的是:事务生效的前提是 MySQL 表引擎为 InnoDB,且连接未被设为自动提交(TP5 默认关闭 auto-commit,但若手动执行过 SET autocommit=1 就会失效)。每次写完自定义事务函数,建议用 Db::getLastSql() 看下实际执行的 SQL 是否真在事务上下文中。

















