Laravel 中如何在数据库事务中延迟队列及事件监听器
如果在数据库事务期间触发了队列或监听,可能会因为数据回滚而导致数据不一致。本文将介绍如何妥善处理。
Laravel 开发者可能会碰到这么一个问题:在数据库事务期间触发了队列或事件监听。这一问题可能导致 ModelNotFoundException
、数据不一致以及其他一些可能影响应用可靠性的问题。本文将说明数据库事务为什么重要、使用时会碰到哪些常见问题以及如何在事务中处理队列和监听。
数据库事务为什么重要?
数据库事务允许你将多个数据库操作组合成单个原子操作。如果事务中的任何一个操作失败了,所有操作都会回滚,以确保数据库的一致性。在 Laravel 中你可以使用 DB:transaction
方法,执行多个数据库操作,实现事务。
参考下例:
use Illuminate\Support\DB;
DB::transaction(function () {
// Perform database queries here
});
此外,如果想要更多控制权,你也可以使用非回调的方式去实现数据库事务:
use Illuminate\Support\Facades\DB;
DB::beginTransaction();
try {
// Perform database operations here
DB::commit();
} catch (\Exception $e) {
DB::rollback();
// Handle the exception here
}
就本文而言,我们会使用前一种方式实现,即回调。
数据库事务常见问题
在 Laravel 中,数据库交易是维护数据一致性的重要手段,不过在使用时,我们可能会碰到一些常见问题,特别是使用队列和事件监听时。在本节中,我们将更详细地讨论这些问题。
队列中的 ModelNotFoundException
队列触发时使用由于回滚而从未保存的模型,也称为作业中的 ModelNotFoundException
。
考虑一个场景,在该场景中,你使用队列创建新用户并向数据库中添加一些记录。但是,如果事务由于错误而回滚,则可能会使用不完整或不存在的数据来调度队列。这可能会导致抛出 ModelNotFoundException
异常,因为队列将尝试访问从未保存过的模型。如果队列负责发送重要通知或执行其他关键操作,这可能会特别有问题。
我在这篇博客文章中谈到了处理 ModelNotFoundException
,但如果使用数据库事务,可能会有更好的解决方案。
事件监听器执行了不能回滚的操作
假如你有一个外部 API 调用,用来与第三方服务同步数据。如果事务回滚了,该操作没办法撤销,可能导致应用和外部服务之间的数据不一致问题。这可能会出现大问题,特别是当外部服务是关键业务的时候。
方案
对于这两个问题,Laravel 有一个强大且简单的方案,确保队列或者事件监听只有在提交数据库事务之后才去执行。
我们一起看一下吧!
事务提交后才派发队列任务
要确保队列任务只有在事务提交后才派发,你可以使用 afterCommit()
方法。该方法只会在事务提交成功才会派发队列任务。示例:
DB::transaction(function () use ($data) {
// Perform database queries here
dispatch(new MyJob($data))->afterCommit();
// 此外,如何队列任务使用了Dispatchable trait:
// MyJob::dispatch($data)->afterCommit();
// Perform other operations that could potentially fail
// and roll back the transaction.
});
此外,如果需要更多控制,你可以使用 DB::afterCommit()
方法,运行一个在事务提交之后调用的回调函数:
DB::transaction(function () use ($data) {
// Perform database queries here
DB::afterCommit(function () {
dispatch(new MyJob($data));
});
// Perform other operations that could potentially fail
// and roll back the transaction.
});
延迟 Laravel 事件监听,使之在事务提交后运行
要保证监听器只会在事件交易后执行,你可以在监听器上设置 afterCommit
属性。该方法将会将监听器的执行延迟到事务提交成功之后。示例:
class SendNotificationListener
{
public $afterCommit = true;
public function handle(MyEvent $event)
{
// Send notification email here
}
}
无论监听器是同步还是异步(队列,实现 ShouldQueque
接口),Laravel 只会在数据库事务提交之后才会执行。
总之,处理数据库事务中的队列任务和监听器需要仔细考虑,以确保应用的数据保持一致。通过使用 afterCommit
方法稍后调度作业,以及在监听器上使用 $afterCommit=true
属性将作业延迟到事务提交之后,可以避免常见问题,如 ModelNotFoundException
和无法回滚的外部 API 调用。使用这些方法,你可以确保 Laravel 应用可靠且一致地运行。