ThinkPHP事务不回滚主因是异常未穿透、连接被切换或引擎不支持;须确保异常穿透、统一PDO连接、使用InnoDB引擎,并用Db::transaction()闭包写法。

ThinkPHP事务不回滚,多数不是框架“坏了”,而是事务上下文没对上——异常没穿透、连接被切换、引擎不支持,三者占了九成原因。关键不在“写了没”,而在“作用到哪条连接、被谁捕获、底层认不认”。
异常必须穿透,不能被静默吞掉
Db::transaction() 依赖未捕获的 \Exception(或 \Throwable)来触发自动回滚。一旦你在闭包里用 try-catch 捕获后没 re-throw,框架就认为“你已处理完毕”,直接 commit。
- ❌ 错误写法:在 Db::transaction() 闭包内 catch 住异常却不 throw
- ✅ 正确做法:去掉闭包内的 try-catch;真要记录日志,末尾加 throw $e;
- ⚠️ PHP 8.0+ 注意:\Error(如 Fatal error、TypeError)不会被 \Exception 捕获,需统一用 \Throwable
Db::startTrans() 在 TP5.1+ 基本不可靠
TP5.1 起启用连接惰性初始化,Db::startTrans() 可能还没拿到 PDO 实例,后续 Db::table() 或模型操作却新建了连接——事务指令发给 A 连接,SQL 执行在 B 连接,rollback() 看似执行了,实则无效。
- ❌ 不推荐:手动 startTrans + commit/rollback 混搭模型、db('table')、Db::connect()
- ✅ 推荐:统一用 Db::transaction(function () { ... }) 闭包写法,它内部确保连接复用
- ? 补充:闭包内想提前退出并回滚?return false;(TP6.0.10+ 支持)
事务生效的前提条件必须逐项核对
事务不是“写了就管用”,它是一组硬性前提共同作用的结果:
立即学习“PHP免费学习笔记(深入)”;
- 数据库表引擎必须是 InnoDB(MyISAM 完全不支持事务),执行 SHOW CREATE TABLE table_name 确认 ENGINE=InnoDB
- 所有操作必须走同一个 PDO 连接实例:禁用 db('table')、Db::connect('slave')、Db::master() 等显式切换连接的操作
- 避免在事务中做耗时操作(HTTP 请求、大文件读写),否则连接可能中断,事务悬空
- 高并发下注意锁顺序,避免死锁;事务范围尽量短,减少行锁持有时间
模型操作也得守规矩
用 UserModel::create() 或 saveAll() 并不自动纳入事务——模型默认使用独立连接实例。即使你先调了 Db::startTrans(),UserModel 和 OrderModel 可能各连各的库,事务只对 Db 门面当前绑定的连接生效。
- ✅ 安全做法:事务内只用 Db::name() 或明确指定同一连接的模型(如 (new UserModel())->connection('default'))
- ✅ 更稳妥:改用 Db::transaction() 闭包,里面统一调 Db::name(),不混用模型静态方法
- ? 提示:模型层验证(如 beforeInsert、验证规则)可提前拦截非法数据,避免插入时才报错导致事务回滚



















