You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 10中MySQL死锁(SQLSTATE[40001])问题排查与修复

Laravel队列死锁(SQLSTATE[40001] 错误码1213)原因分析与修复方案

原因分析

死锁的核心是两个或多个数据库操作互相持有对方需要的锁,形成循环等待,结合你的场景,具体触发逻辑如下:

  • Laravel数据库队列的worker处理任务时,会先执行select * from jobs where queue = ? and reserved_at is null order by id asc limit 1 for update锁定待处理任务,更新reserved_at标记为已占用,任务完成后执行delete from jobs where id = ?删除任务。
  • 你的两个定时命令都往默认队列批量分发Job,在任务分发高峰期,多个worker会同时竞争队列中的任务:
    1. Worker A通过select ... for update锁定了任务ID 1,准备执行删除但尚未完成;
    2. Worker B同时通过select ... for update锁定了任务ID 2;
    3. 若此时出现锁顺序反转(比如Worker A尝试获取ID 2的锁,Worker B尝试获取ID 1的锁),或InnoDB的间隙锁(因批量插入任务产生的范围锁)与行锁冲突,就会触发循环等待,导致死锁。
  • 另外,默认的REPEATABLE READ事务隔离级别下,InnoDB会生成更多间隙锁,进一步增加了死锁的触发概率。

修复方案

1. 拆分队列,减少锁竞争

将两个定时命令的Job分发到独立队列,避免不同类型的任务在同一队列中竞争锁:

  • 修改定时命令的分发代码,指定不同队列:
    // GoogleIndexSearchedCarsCommand
    GoogleIndexSearchedJob::dispatch($car)->onQueue('google-searched');
    // GoogleIndexHiddenCarsCommand
    GoogleIndexHiddenJob::dispatch($car)->onQueue('google-hidden');
    
  • 启动对应队列的worker:
    php artisan queue:work --queue=google-searched
    php artisan queue:work --queue=google-hidden
    

2. 启用skip_locked跳过锁定任务(MySQL 8.0+)

在config/queue.php的database连接配置中添加skip_locked参数,让worker直接跳过已被其他进程锁定的任务,避免等待锁引发死锁:

'connections' => [
    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90,
        'skip_locked' => true, // 新增此配置
    ],
],

3. 优化批量Job插入逻辑

如果定时命令是批量生成Job,使用数据库批量插入替代单个插入,减少事务次数和锁竞争:

// 替代多次单个dispatch,批量组装任务数据后插入
$jobData = collect($cars)->map(function ($car) {
    return [
        'queue' => 'google-searched',
        'payload' => json_encode([
            'job' => '\App\Jobs\GoogleIndexSearchedJob',
            'data' => ['car' => $car]
        ]),
        'created_at' => now(),
        'updated_at' => now(),
    ];
})->toArray();

DB::table('jobs')->insert($jobData);

4. 调整数据库事务隔离级别

将MySQL的事务隔离级别从默认的REPEATABLE READ改为READ COMMITTED,减少间隙锁的产生:
在config/database.php的mysql连接中添加:

'connections' => [
    'mysql' => [
        // 其他配置
        'isolation_level' => 'READ COMMITTED',
    ],
],

5. 增加Job重试机制

在Job类中添加重试配置,让死锁导致失败的任务自动重试(死锁通常是瞬时的):

class GoogleIndexSearchedJob implements ShouldQueue
{
    public $tries = 3; // 重试次数
    public $backoff = [1, 3, 5]; // 重试间隔(秒)
    
    // 任务逻辑...
}

6. 合理调整worker数量

避免启动过多的worker进程竞争同一队列,根据服务器CPU核心数和队列任务量调整,一般worker数不超过CPU核心数的2倍。

内容的提问来源于stack exchange,提问作者Nikolai92

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:24:58