爱名网(22科技集团)

Laravel 事务提交后再派发队列

2026-08-18 10:00老强

Laravel 事务提交后再派发队列

Laravel 里写队列很顺手。订单创建成功,发一个短信任务;表单提交成功,发一个同步任务;支付回调处理完,发一个通知任务。

代码看起来没问题,但在队列跑得很快、数据库压力比较大,或者读写库有延迟时,它会出一些很难复现的问题。

一、问题在哪里

事务还没提交,队列任务可能已经开始跑了。

DB::transaction(function () use ($request) {
    // 创建订单主记录
    $order = Order::create([
        'order_no' => $request->input('order_no'),
        'status' => 'pending',
    ]);

    // 派发订单后续处理任务
    ProcessOrderJob::dispatch($order->id);
});
1. Laravel 开启事务
2. 写入订单
3. dispatch 队列任务
4. worker 拿到任务并开始执行
5. worker 查询订单
6. 事务还没提交,订单查不到
7. 事务提交

二、不要在事务中间急着 dispatch

最简单的做法,是把任务派发放到事务后面。

$order = null;

DB::transaction(function () use ($request, &$order) {
    // 事务里只做数据库写入
    $order = Order::create([
        'order_no' => $request->input('order_no'),
        'status' => 'pending',
    ]);
});

// 事务提交后,再派发队列任务
ProcessOrderJob::dispatch($order->id);

三、afterCommit 更直接

如果项目版本支持,可以让队列任务等事务提交后再派发。

DB::transaction(function () use ($request) {
    // 创建订单主记录
    $order = Order::create([
        'order_no' => $request->input('order_no'),
        'status' => 'pending',
    ]);

    // 等当前事务提交后,再真正派发任务
    ProcessOrderJob::dispatch($order->id)->afterCommit();
});

四、任务里不要传整块模型

队列任务里尽量传 ID,不要传一整块模型。

不推荐:
ProcessOrderJob::dispatch($order);

更稳一点:
ProcessOrderJob::dispatch($order->id);
public function handle()
{
    // 任务执行时重新读取最新订单数据
    $order = Order::find($this->orderId);

    if (!$order) {
        return;
    }

    // 继续处理订单
}

五、任务最好是幂等的

事务提交后再派发,只能解决“读不到未提交数据”的问题。队列任务本身还可能重复执行。

public function handle()
{
    // 重新读取订单,避免使用过期状态
    $order = Order::find($this->orderId);

    if (!$order) {
        return;
    }

    // 已处理过的订单直接跳过
    if ($order->status === 'processed') {
        return;
    }

    // 执行业务处理
    $this->process($order);

    // 标记处理完成
    $order->update([
        'status' => 'processed',
    ]);
}

六、多个任务怎么处理

事务里有时候会做很多事。这时候可以先收集任务参数,事务外统一派发。

$jobs = [];

DB::transaction(function () use ($request, &$jobs) {
    // 创建订单主记录
    $order = Order::create([
        'order_no' => $request->input('order_no'),
        'status' => 'pending',
    ]);

    // 只收集任务需要的参数,不在事务中间派发
    $jobs[] = new ProcessOrderJob($order->id);
    $jobs[] = new SendOrderNoticeJob($order->id);
});

foreach ($jobs as $job) {
    dispatch($job);
}

七、什么时候可以在事务里派发

不是所有任务都必须等事务提交。关键看任务会不会读取事务里刚写入的数据。

任务会读事务里写的数据:提交后派发
任务不会读事务里写的数据:可以按实际情况处理
拿不准:提交后派发

八、最后

Laravel 的事务和队列都很好用。坑通常不在工具本身,而在两个工具接在一起时,执行顺序没有想清楚。

事务里只做数据库写入
队列任务等事务提交后再派发
任务参数尽量传 ID
任务执行时重新查数据
任务逻辑尽量幂等

这几条看起来朴素,但线上很管用。

曝光355浏览28