必须使用数据库事务保障数据一致性,DB::transaction提供闭包自动管理、手动控制、保存点模拟嵌套、事件延迟提交及死锁重试五种策略。

如果您在Laravel中执行多个数据库操作,且需要确保这些操作要么全部成功、要么全部回滚,则必须使用数据库事务来保障数据一致性。DB::transaction提供了一种简洁、安全的方式来开启和管理事务。以下是具体实现方式:
一、使用闭包方式自动管理事务
该方法由Laravel自动处理提交与回滚:当闭包内无异常时自动提交;抛出Exception或Throwable时自动回滚,无需手动调用commit或rollback。
1、在控制器或服务类中引入DB门面:use Illuminate\Support\Facades\DB;
2、调用DB::transaction并传入一个闭包函数,将所有需原子执行的数据库操作写入其中:
3、在闭包内执行多条DB操作,例如插入用户记录并关联创建配置项:
4、若任意一步失败(如唯一约束冲突、字段缺失、数据库连接中断),整个事务将立即回滚,原始数据保持不变。
二、手动控制事务生命周期
适用于需要在运行时根据条件决定是否提交或回滚的场景,例如分步校验、外部API调用后决策等。开发者需显式调用DB::beginTransaction()、DB::commit()和DB::rollback()。
1、调用DB::beginTransaction();显式开启事务。
2、执行第一条数据库写入操作,例如更新订单状态为“处理中”。
3、执行第二条操作,例如扣减库存,同时捕获可能的QueryException:
4、若所有操作成功,调用DB::commit();提交事务。
5、若任一操作失败,立即调用DB::rollback();回滚已执行的更改。
三、嵌套事务的处理策略
Laravel默认不支持真正的嵌套事务,外层事务的回滚会覆盖内层所有变更。但可通过保存点(Savepoint)模拟嵌套行为,使内层失败仅回滚至保存点,而不影响外层逻辑。
1、在外层事务中调用DB::transaction(function () { ... });启动主事务。
2、在闭包内部,使用DB::statement('SAVEPOINT sp1');创建命名保存点。
3、执行可能失败的子操作,例如向日志表写入调试信息。
4、若子操作异常,执行DB::statement('ROLLBACK TO SAVEPOINT sp1');仅回滚该段。
5、主事务仍可继续执行后续语句,并在闭包结束时正常提交。
四、事务中处理模型事件与观察者
当在事务中调用Eloquent模型的save()、create()等方法时,模型事件(如creating、created)及注册的观察者会正常触发,但其副作用(如发送邮件、写入日志文件)可能破坏事务一致性。
1、避免在模型事件处理器中执行非数据库操作,例如Mail::send()或Log::info()。
2、若必须触发外部动作,应将其延迟至事务成功提交后执行,例如监听DB::afterCommit()回调。
3、在事务闭包内注册提交后钩子:DB::afterCommit(fn() => event(new OrderPlaced($order)));
4、确保事件监听器本身不发起新的数据库写入,否则可能超出当前事务上下文。
五、事务超时与死锁应对
长时间运行的事务可能引发连接超时或行级锁竞争,尤其在高并发更新同一数据集时易出现死锁。需主动设置超时并捕获相应异常。
1、在配置文件config/database.php中为对应数据库连接设置'timeout' => 30(单位:秒)。
2、在事务闭包外使用try-catch捕获Illuminate\Database\QueryException。
3、检查异常消息是否包含“Deadlock found”字符串,若是,则可选择重试逻辑。
4、实现简单重试机制:在循环中执行事务,捕获死锁异常后延迟随机毫秒数再重试,最多不超过3次。










