thinkphp事务不会自动回滚,需显式调用rollback()或确保异常穿透;db::transaction()仅对未被捕获异常生效,嵌套无效,且依赖innodb引擎、正确连接状态与异常重抛机制。

ThinkPHP 的事务不会自动回滚,哪怕抛出异常、脚本中断或 return 退出,只要没显式调用 rollback() 或闭包未让异常穿透,数据就可能已提交。
Db::transaction() 闭包里为什么有时不回滚
它只对「未被捕获的异常」生效。一旦你在闭包里写了 try-catch 却没 throw $e,框架根本收不到错误信号,会默认执行 commit()。
- ❌ 错误写法:
Db::transaction(function () { try { Db::table('order')->insert([...]); } catch (\Exception $e) { Log::error($e); } });—— 日志写了,数据也提交了 - ✅ 正确写法:去掉
try-catch,或在catch末尾加throw $e; - ⚠️ 注意:
Db::transaction()不支持嵌套,外层再套一层会静默失效;也不兼容自定义set_error_handler,若项目关了error_reporting,可能连语法错误都捕获不到
startTrans() + commit()/rollback() 手动模式容易漏哪几步
手动控制自由度高,但每一步都是单点故障:开启、判断、提交、回滚,缺一不可。
- 忘记在
catch块里写Db::rollback()—— 数据已落库,且无法挽回 - 用了多个模型(如
$user和$order),但只对其中一个调startTrans()—— 另一个操作完全游离于事务之外 - 事务中混用原生
PDO实例或第三方 SDK 的数据库调用 —— 它们不走 ThinkPHP 连接池,不受事务控制 - 没重抛异常:
catch里只记录日志就结束,上层逻辑会误判为成功
事务回滚失败的底层硬伤:MyISAM 和连接状态
表引擎不是 InnoDB,所有事务操作都白忙——startTrans()、rollback() 全部静默失效,SQL 照样立即提交。
- 检查方式:
SHOW CREATE TABLE `order`;看ENGINE=InnoDB是否存在 - 调试时用了
dd()或dump()中断流程 —— PDO 连接卡在 “transaction active” 状态,后续请求可能报There is no active transaction - 回滚后直接执行新查询,可能报
Cannot execute queries while other unbuffered queries are active—— 回滚不重置连接状态,某些死锁或超时会让连接进入半损坏状态
死锁场景下不能靠 Db::transaction() 自动兜底
Db::transaction() 不识别死锁错误类型,遇到 Deadlock found when trying to get lock 仍会触发回滚,但不会重试。
- 必须手动捕获
PDOException,用str_contains($e->getMessage(), 'Deadlock')判断 - 重试要带退避延迟:
usleep(50000 * ($i + 1)),否则瞬间重试等于重复撞锁 - 最大重试次数必须设限,盲目重试会放大并发冲突
- 关键操作前加行锁:
lock(true)(即SELECT ... FOR UPDATE),避免无锁更新引发隐式锁升级
最常被忽略的是:事务是否真正生效,从来不能靠“代码写了就放心”,而要靠 throw new \Exception('test') 验证数据是否消失,以及确认表引擎、连接复用、调试中断这三道坎全跨过去。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











