Laravel数据库事务与队列

2026-09-09 44

创建订单后派发队列任务发送通知,是 Laravel 项目中的常见流程。但在异步队列环境中,任务有时会报“订单不存在”,重试后又恢复正常。一个容易忽略的原因是,队列工作进程已经开始执行任务,而创建订单的数据库事务还没有提交。

Laravel 提供的 afterCommit() 可以将任务派发推迟到事务成功提交之后,避免任务过早读取尚未提交的数据。下面以创建订单后派发任务为例,介绍它的用法、全局配置和验证方式。

一、为什么队列任务会先于事务提交执行

下面的代码在事务内部创建订单,并立即派发任务:

use App\Jobs\ProcessOrder;
use App\Models\Order;
use Illuminate\Support\Facades\DB;

$order = DB::transaction(function () {
    $order = Order::create([
        'user_id' => auth()->id(),
        'status' => 'pending',
    ]);

    ProcessOrder::dispatch($order->id);

    return $order;
});

示例假设订单模型允许写入 user_idstatus 字段,相关代码运行在已经完成身份验证的请求中。

当队列连接没有启用提交后派发,且任务进入 Redis 等独立队列后,执行顺序可能是:

  1. 主请求在数据库事务内插入订单。
  2. 任务进入队列。
  3. 队列工作进程领取任务并查询订单。
  4. 主请求提交数据库事务。

第三步发生时,工作进程使用的数据库连接通常看不到另一个连接尚未提交的数据,因此可能查询失败。

是否出现这个问题,与队列驱动、数据库连接和事务配置有关。不能因为本地使用同步队列没有报错,就认为生产环境的异步队列也不会出现问题。

二、为单个任务添加 afterCommit

如果只需要调整某个任务,直接在派发时调用 afterCommit()

use App\Jobs\ProcessOrder;
use App\Models\Order;
use Illuminate\Support\Facades\DB;

$order = DB::transaction(function () {
    $order = Order::create([
        'user_id' => auth()->id(),
        'status' => 'pending',
    ]);

    ProcessOrder::dispatch($order->id)->afterCommit();

    return $order;
});

此时,Laravel 会等待相关事务成功提交,再将任务派发到队列。如果相关事务回滚,这次等待提交的任务派发也会被丢弃。

如果调用时没有正在进行的事务,任务正常派发,不会一直等待。

需要注意,afterCommit() 控制的是派发时机。任务进入队列后,仍然需要工作进程领取和执行,并不保证提交后立即处理完成。

三、编写一个可观察执行结果的任务

创建任务:

php artisan make:job ProcessOrder

在 Laravel 11、12 的常见项目结构中,可以编写:

namespace App\Jobs;

use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class ProcessOrder implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public int $orderId
    ) {
    }

    public function handle(): void
    {
        $order = Order::findOrFail($this->orderId);

        Log::info('Order job loaded committed order', [
            'order_id' => $order->id,
            'status' => $order->status,
        ]);
    }
}

这个示例只查询订单并记录日志,方便确认任务能够读取已经提交的数据。实际项目可以在查询成功后加入邮件通知、报表生成等逻辑。

示例通过订单 ID 传递任务参数,便于观察任务执行时的数据库查询。但传递 ID 本身不能解决事务时序问题,关键仍然是提交后派发。

四、通过 after_commit 配置统一启用

如果项目中大量任务都依赖事务内写入的数据,可以在对应队列连接中启用 after_commit

例如,在 config/queue.phpconnections.redis 配置中,将现有设置调整为:

'after_commit' => true,

这只是需要修改的配置项,应保留该连接的其他设置。

启用后,使用该队列连接派发的任务会等待相关数据库事务提交。该设置也影响使用此连接的队列监听器、队列邮件、通知和广播事件。

配置只对实际使用的队列连接生效。 如果项目使用 database 连接,仅修改 redis 下的配置不会起作用。

生产环境使用配置缓存时,修改后需要重新生成缓存,并让长驻工作进程加载新配置:

php artisan config:cache
php artisan queue:restart

queue:restart 会通知常规队列工作进程在完成当前任务后退出。生产环境应由 Supervisor 或其他进程管理工具负责重新启动;使用 Horizon 时,应采用对应的 Horizon 重启流程。

五、事务回滚时,任务会怎样处理

可以在测试环境中通过主动抛出异常验证回滚行为:

use App\Jobs\ProcessOrder;
use App\Models\Order;
use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $order = Order::create([
        'user_id' => auth()->id(),
        'status' => 'pending',
    ]);

    ProcessOrder::dispatch($order->id)->afterCommit();

    throw new \RuntimeException('Rollback test');
});

这段代码预期抛出异常,并触发事务回滚。验证时应确认:

  • 数据库中没有保留这次创建的订单。
  • 这次等待提交的任务没有进入队列。
  • 没有出现对应订单的任务执行日志。

该示例用于测试,不应保留在正常业务流程中。

六、验证提交后派发是否生效

使用已经配置完成的异步队列连接,在测试环境启动工作进程:

php artisan queue:work --tries=3

然后执行正常创建订单的业务请求,检查订单记录、队列工作进程输出和应用日志。

如果应用使用单文件日志通道,可以查看:

tail -f storage/logs/laravel.log

其他日志通道应查看相应的日志文件或日志平台。

建议至少覆盖以下情况:

验证场景 预期结果
事务成功提交 任务正常派发,可以查询到订单
事务回滚 等待提交的任务不派发
没有活动事务 任务正常派发
工作进程尚未启动 任务进入队列,等待领取

只使用 Queue::fake() 检查“是否派发过任务”,不足以验证真实工作进程能否读取已提交数据。这个问题涉及独立进程和数据库事务可见性,适合补充真实异步队列的集成验证。

七、afterCommit 与重试、幂等有什么区别

这三项机制处理的问题不同:

机制 主要作用
afterCommit() 防止任务在相关事务提交前被派发
任务重试 在执行失败后重新尝试
幂等处理 避免重复执行造成重复业务结果

例如,订单提交后派发通知任务,邮件服务可能暂时不可用,此时仍需要重试。任务也可能因超时或工作进程中断而再次执行,因此通知、扣款、库存变更等操作还需要按业务要求设计幂等控制。

此外,数据库提交和向 Redis 等队列投递任务并不是一个原子操作。即使使用 afterCommit(),仍可能发生“订单已经提交,但任务投递失败”的情况。对不能遗漏的业务事件,可以进一步采用事务消息表(Outbox)和补偿投递机制。

八、常见问题

使用 afterCommit 后,能保证任务一定查到订单吗?

不能完全保证。它解决事务尚未提交这一类时序问题,但订单后续被删除、数据库读写分离延迟、租户上下文错误等情况,仍然可能导致查询失败。

设置 after_commit 后,还需要逐个调用 afterCommit 吗?

如果任务已经使用启用了 after_commit 的连接,通常不需要重复设置。单独调用适合只对部分任务启用提交后派发的场景。

可以用延迟几秒代替 afterCommit 吗?

不建议。固定延迟无法保证事务已经提交,事务变慢时仍然可能出错。对于依赖事务内数据的任务,应明确使用提交后派发,再按实际业务需要决定是否增加延迟。

  • 广告合作

  • QQ群号:4114653

温馨提示:
1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。 2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。