ThinkPHP事务需手动commit或rollback,否则请求结束自动回滚;Db::transaction()最安全,startTrans()不支持嵌套,跨库事务无效,事务内操作须用同一连接。

ThinkPHP 的事务不是“开了就自动生效”,必须显式提交或回滚,否则请求结束时会自动 rollback —— 这是绝大多数人踩坑的根源。
事务必须手动 commit 或 rollback
ThinkPHP 的 startTrans() 只是开启事务,并不会自动提交。哪怕你执行了多条 save()、delete(),只要没调用 commit(),数据就不会落库;而如果没调用 rollback() 且发生异常,框架也不会帮你兜底。
- 必须在业务逻辑成功后显式调用
commit() - 必须在 catch 块中显式调用
rollback() - 不能依赖“脚本结束自动提交”——TP 不提供该行为
- 若忘记写
commit(),日志里看不到错误,但数据就是不写入
模型层事务 vs Db 类事务的区别
用 Db::transaction() 是最安全的封装方式,它内部已帮你 try/catch 并自动 rollback;而直接在模型上调用 startTrans() + commit() 需自己处理异常流,容易遗漏。
-
Db::transaction(function () { ... }):推荐,自动管理,失败即 rollback -
UserModel::startTrans()+ 手动commit()/rollback():需自行捕获所有异常分支 - 跨模型操作(如同时操作
User和Order)建议统一用Db::transaction(),避免事务作用域不一致 - 注意:不同模型若使用不同数据库连接,
startTrans()无法跨库生效
嵌套事务在 ThinkPHP 中不被支持
ThinkPHP 的事务底层基于 PDO,并未实现保存点(savepoint),因此 startTrans() 多次调用不会形成嵌套,而是覆盖前一次状态。所谓“内层事务回滚只影响局部”在 TP 里不存在。
立即学习“PHP免费学习笔记(深入)”;
- 第二次
startTrans()会重置事务状态,之前的commit()或rollback()逻辑可能失效 - 不要在 A 方法里 startTrans,再调用 B 方法(B 也 startTrans)——B 的事务会干扰 A
- 需要条件性回滚局部操作?改用业务层标记 + 提前 return,而不是靠嵌套事务
- 真要模拟保存点,得自己用
Db::execute('SAVEPOINT sp1')+ROLLBACK TO sp1,但需确保驱动支持且手动管理
事务中查询结果可能不一致
在事务未提交前,其他请求查不到你刚写入的数据(默认 RR/RC 隔离级别下),但你自己当前连接能查到;不过如果你在事务中混用 Db::query() 和模型查询,可能因连接实例不同导致读不到刚写的记录。
- 事务内所有操作尽量走同一个连接:优先用模型方法(它们默认复用事务连接),少用
Db::table()->xxx()混搭 - 避免在事务中调用另一个未指定连接的模型方法,它可能走新连接,读不到未提交数据
- 调试时可用
Db::getLastSql()确认是否真的跑在事务连接上 - 高并发下注意:长事务会拖慢数据库,
SELECT ... FOR UPDATE类语句要格外小心锁范围
事务的边界意识比语法更重要:哪几行代码必须原子执行、哪些异常必须触发回滚、有没有隐藏的连接切换——这些地方一松动,事务就形同虚设。



















