ThinkPHP事务不会自动回滚,必须显式调用rollback()或使用Db::transaction()闭包;startTrans()需手动配对commit/rollback;MyISAM引擎不支持事务,须确认表为InnoDB。

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()不支持嵌套,外层闭包里再调一次会报错或行为不可控
startTrans() + try-catch 手动控制:灵活但易漏 rollback()
适用于需条件判断、跨模型操作、或要自定义回滚逻辑的场景,但必须严格配对:
立即学习“PHP免费学习笔记(深入)”;
$user = Db::name('user');
$order = Db::name('order');
<p>$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()—— 另一个模型的操作不受事务保护 - 事务开启后,中间调用了非 Db 类的数据库操作(如原生 PDO 实例、第三方 SDK),它们完全游离于 ThinkPHP 事务之外
MyISAM 表引擎导致 rollback() 完全无效
这是最容易被忽略的底层硬伤:MyISAM 不支持事务,startTrans()、commit()、rollback() 全部静默失效,看起来“执行成功”,实则每条 SQL 都是立即提交。
验证和修复方法:
- 查表引擎:
SHOW CREATE TABLE `order`;看输出中是否含ENGINE=InnoDB - 批量转引擎(MySQL):
ALTER TABLE `order` ENGINE=InnoDB; - 建表时明确指定:
CREATE TABLE `log` (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - TP 迁移文件中也要写
$table->engine = 'InnoDB';
事务未提交却结束请求:PDO auto-commit 的陷阱
ThinkPHP 默认使用 PDO 的 auto-commit 模式。这意味着:如果事务开启后没调 commit() 也没调 rollback(),PHP 脚本结束时 PDO 会自动提交——不是回滚。
所以这些写法都危险:
- 只写
startTrans(),然后return或exit,没走commit/rollback - 在
try里执行完所有操作,但忘了写commit(),也无catch - 用
Db::transaction()闭包,但闭包内逻辑被if条件提前退出(比如return),没走到后续 SQL
本质是:ThinkPHP 事务依赖 PDO 的底层状态,而 PDO 不会在脚本终止时帮你“安全兜底”。你得自己确保控制流覆盖所有出口路径。



















