laravel 11 中 db::transaction() 内未捕获的 throwable 异常会自动回滚,但 try/catch 捕获、die/exit、致命错误、pdo 错误模式未启用等情况均不触发回滚;嵌套事务无独立回滚边界,超时需手动防护。

Laravel 11 中,DB::transaction() 闭包内抛出异常时,事务**确实会自动回滚**,但这个行为有明确的边界条件——不是所有“看起来像失败”的情况都会触发回滚。关键不在“有没有错”,而在于“异常是否穿透闭包、被 Laravel 的事务管理器捕获到”。
异常必须未被捕获且属于 Throwable 子类
DB::transaction 内部用 try/catch 包裹闭包,只对未处理的 Throwable(包括 Exception 和 Error)做回滚。以下情况**不会回滚**:
- 在闭包里用
try/catch捕获了异常,又没重新throw—— 事务会照常提交 - 使用
return、dd()、exit或die()中断流程 —— 不是异常,不触发回滚机制 - 发生 PHP 致命错误(如调用未定义函数、内存耗尽)—— 绕过异常处理器,回滚失效
-
数据库驱动未启用
PDO::ERRMODE_EXCEPTION—— SQL 约束错误(如唯一键冲突、外键失败)静默忽略,不抛异常,自然不回滚
事务生命周期严格绑定闭包执行完成状态
回滚与否,只取决于闭包是否“正常返回”。Laravel 不检查 SQL 执行结果,也不监听 MySQL 错误码:
- 闭包执行完毕、无异常 → 自动
commit() - 闭包中途抛出未捕获的
Throwable→ 自动rollback()并重抛 - 闭包里调用
saveOrFail()、updateOrFail()或验证失败抛ValidationException→ 符合条件,会回滚 - 闭包中执行 HTTP 请求、Redis 写入、文件操作等 —— 这些成功或失败都不影响 DB 回滚逻辑,但会造成业务语义不一致
嵌套事务不构成独立回滚边界
Laravel 11 的 DB::transaction() 不支持真正嵌套事务。连续调用只是增加内部计数器,底层仍是单层 BEGIN:
- 外层闭包抛异常 → 全部回滚
- 内层闭包抛异常,但被外层
try/catch吞掉 → 内层变更已实际写入,无法局部撤销 - 想实现局部回滚,必须显式用
DB::beginTransaction()+DB::rollBackTo('sp_name')配合保存点,且需手动管理
超时与连接中断不会被自动感知
事务本身没有内置超时机制。若闭包执行卡住(比如循环处理大量数据、等待外部 API),可能触发 MySQL 的 wait_timeout 或 innodb_lock_wait_timeout,导致连接断开:
- 连接断开后,PHP 层可能抛
PDOException,此时仍能回滚(因属Throwable) - 但如果断开后程序未及时报错(例如静默重连),后续
commit()可能失败或无响应,事务状态不可控 - 建议在闭包开头加
set_time_limit(30),或用microtime(true)做主动耗时判断











