编程

Laravel 中如何在数据库事务中延迟队列及事件监听器

1231 2023-04-29 05:41:00

如果在数据库事务期间触发了队列或监听,可能会因为数据回滚而导致数据不一致。本文将介绍如何妥善处理。

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 应用可靠且一致地运行。