事务没生效主因是异常未透出闭包或数据库引擎非innodb:db::transaction()内需让异常穿透触发回滚,myisam引擎完全忽略事务指令,嵌套调用不产生子事务,查不到刚插入数据多因连接未复用。

事务没生效,八成是异常没透出闭包,或者表引擎不是 InnoDB。Laravel 的事务机制本身很稳,问题通常出在边界控制或底层配置上。
DB::transaction() 为什么有时不回滚
闭包里抛了异常,但被 try/catch 吞掉了,或者用 return false、if (! $model->save()) { } 这类静默退出,事务就照常提交——Laravel 只认未捕获的 Throwable。
- 模型
save()返回false≠ 抛异常,得自己判断后throw new RuntimeException() - 手动校验失败(如库存不足)必须显式
throw,不能只return - 闭包内调用其他服务方法时,确保它们也不吞异常;否则外层收不到错误信号
- 云数据库(如阿里云 RDS)可能默认用
MyISAM,SHOW CREATE TABLE users看引擎,不是InnoDB就白搭
手动 beginTransaction/commit/rollback 的安全写法
适合需要日志、通知、条件提交等扩展逻辑的场景,但必须用 try/catch 包裹,否则事务可能卡在“开启未提交”状态。
- 开头调用
DB::beginTransaction() - 所有写操作放进
try块,末尾加DB::commit() -
catch块第一行必须是DB::rollback(),之后再throw $e或report($e) - 别在
catch里只记录日志却不回滚——那数据已经脏了 - 如果事务中要查刚插入的数据,确保复用同一连接:
DB::connection()->getPdo()要一致,否则可能查不到
SAVEPOINT 局部回滚怎么写才不报错
想在大事务里对某段子逻辑单独回滚,不能靠嵌套 DB::transaction()——它不创建新事务,只增计数器,内层 rollback() 会直接报 There is no active transaction。
- 先开事务:
DB::beginTransaction() - 建保存点:
DB::statement('SAVEPOINT sp_inventory') - 执行可能失败的操作(如扣库存 + 生成物流单)
- 失败时回滚到点:
DB::statement('ROLLBACK TO SAVEPOINT sp_inventory') - 注意:PDO 模拟预处理开启时,
SAVEPOINT可能失效,需关掉:'options' => [PDO::ATTR_EMULATE_PREPARES => false]
原生 SQL 和 Eloquent 混用事务要注意什么
可以混,但得在同一事务上下文里,且所有操作必须走同一个数据库连接实例。
-
DB::transaction()闭包里既能写User::create(),也能写DB::statement('UPDATE ...'),都受保护 - 避免在事务中调用
DB::select()后再用不同连接做update——可能读到旧值或触发锁等待 - 事务中不要 dispatch 队列、发 HTTP 请求、写文件,这些操作不受事务控制,失败后无法撤回
- 如果要用
DB::unprepared()执行多语句脚本,必须放在事务内,且确认数据库支持(MySQL 5.7+ OK,低版本慎用)
最易被忽略的是表引擎和异常穿透——哪怕代码逻辑再严谨,MyISAM 表上跑 DB::transaction() 就等于没写,而闭包里 catch 之后忘了 throw $e,就只剩半截数据躺在库里。











