db::transaction() 闭包方式最常用但有隐含限制:仅捕获 exception/throwable,不处理 save() 返回 false 或致命错误;必须将所有相关操作置于闭包内,throw new exception 才触发回滚,支持返回值与重试次数配置。

事务在 Laravel 中不是“要不要用”的问题,而是“怎么用对”的问题——用错方式会导致回滚失效、数据不一致,甚至死锁。
DB::transaction() 闭包方式最常用但有隐含限制
这是最简洁的写法,DB::transaction() 内部自动调用 beginTransaction()、commit() 或 rollback(),但只响应 Exception 和 Throwable。它不会捕获 PHP 的致命错误(如内存溢出)、也不会拦截模型 save() 返回 false 这类逻辑失败。
- 必须把所有相关操作写在闭包内,不能拆到外部函数里——闭包外的数据库调用会脱离事务上下文
- 闭包中调用
throw new \Exception('xxx')才能触发回滚;仅return false不起作用 - 支持返回值:
$result = DB::transaction(fn () => User::create([...])),$result就是创建的模型实例 - 可传第二个参数控制重试次数:
DB::transaction(..., 2),适用于临时性数据库抖动场景
手动 beginTransaction / commit / rollback 必须配 try-catch
手动方式灵活,但也最容易漏掉回滚——比如 catch 块里忘了写 DB::rollback(),或者异常被上层吞掉,事务就一直挂着,可能锁住表。
- 务必用
try { ... DB::commit(); } catch (\Throwable $e) { DB::rollback(); throw $e; }结构,不能只写catch不抛出 - 执行
DB::rollback()前建议检查DB::transactionLevel() > 0,避免在未开启事务时调用导致报错 - 如果事务里要调用外部 API 或发邮件,必须放在
commit之后——这些操作不受事务保护,提前做可能造成“数据已回滚但邮件已发出”
事务里别碰视图写操作,也别依赖模型事件做关键动作
对数据库视图执行 insert() 或 update() 几乎必然失败,MySQL 报 The target table xxx of the INSERT is not insertable-into,PostgreSQL 也会拒绝。而模型事件(如 created)虽然能触发,但其副作用(如写文件、发通知)无法随事务回滚。
- 查视图没问题,但写操作必须落到基础表,Eloquent 模型里显式设
protected $table = 'real_table_name' - 审计日志这类强一致性要求的操作,得和业务操作一起进同一个
DB::transaction()闭包,不能靠created事件异步写入 - 用
upsert()批量写入时,确保唯一索引已建好,否则冲突检测失效,事务可能部分成功部分失败
真正容易被忽略的点是连接复用:Laravel 默认每个 DB::transaction() 都复用当前连接,但如果代码里混用了 DB::connection('other'),那个连接不在事务范围内——事务不是全局的,它只绑定到某一个数据库连接实例。











