发布于2026-07-07 阅读(0)
扫一扫,手机访问
你写了一段代码,异常抛了,脚本停了,数据却已经进了库——不是 ThinkPHP 在偷懒,而是事务的底层逻辑决定了它不会自动回滚。哪怕你抛出异常、脚本中断或直接 return,只要没显式调用 rollback(),数据就可能已提交,尤其在 PDO auto-commit 模式下。这可不是一个“失败即回滚”的兜底机制,是需要你主动把控的流程。

先从最简洁的写法聊起,也是官方推荐的入门方式——Db::transaction() 闭包。语法干净利落,异常时能自动回滚:
Db::transaction(function () { Db::table('order')->insert(['uid' => 1, 'amount' => 99.9]); Db::table('log')->insert(['action' => 'create_order']);});
但注意,这个“自动回滚”只对闭包内未被捕获的异常生效。一旦你在闭包里写了 try-catch,却没 throw 或 re-throw,回滚就不会触发——这是最常见的“隐藏前提”。
try-catch,让异常自然冒泡catch 了异常但只打日志、没 throw $e,事务照常提交,等于白忙一场Db::transaction() 不支持嵌套,外层闭包里再调一次会报错或行为完全失控当你需要条件判断、跨模型操作,或者要自定义回滚逻辑时,手动控制才更顺手。但必须严格配对,少一步都不行:
$user = Db::name('user');$order = Db::name('order');$user->startTrans();try {$user->update(['balance' => ['exp', 'balance - 100']]);$order->insert(['uid' => 1, 'status' => 'paid']);$user->commit();} catch (\Exception $e) {$user->rollback();throw $e; // 建议重抛,避免上层误判为成功}
写这部分代码时,容易出问题的点不难想到:
catch 块里写 $model->rollback() —— 数据直接落库$user 和 $order),但只对其中一个调 startTrans() —— 另一个模型的操作完全游离在事务保护之外这算是底层硬伤中最容易被忽视的:MyISAM 引擎不支持事务。你的 startTrans()、commit()、rollback() 全都会静默失效。看起来“执行成功”,实则每条 SQL 都是立即提交。
验证和修复的方法并不复杂:
SHOW CREATE TABLE `order`; 看输出中是否含 ENGINE=InnoDBALTER TABLE `order` ENGINE=InnoDB;CREATE TABLE `log` (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;$table->engine = 'InnoDB';还有一个容易被忽略的细节:事务未提交却结束请求时,PDO auto-commit 会带来陷阱。ThinkPHP 默认使用 PDO 的 auto-commit 模式,这意味着:如果事务开启后没调 commit() 也没调 rollback(),PHP 脚本结束时 PDO 会自动提交——不是回滚。
所以这些写法都够危险:
startTrans(),然后 return 或 exit,没走 commit/rollbacktry 里执行完所有操作,但忘了写 commit(),也无 catchDb::transaction() 闭包,但闭包内逻辑被 if 条件提前退出(比如 return),没走到后续 SQL说到底,ThinkPHP 事务依赖的是 PDO 的底层状态。而 PDO 不会在脚本终止时帮你“安全兜底”。你得自己确保控制流覆盖所有出口路径,这一点,比写出正确的 SQL 更考验对框架原理的理解。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8