Laravel中处理数据库死锁的五种策略:一是DB::transaction()内置重试;二是手动捕获死锁异常并重试;三是结合引用传递追踪重试次数;四是配置数据库连接层超时与隔离级别;五是使用队列异步化事务执行。

一、使用DB::transaction()内置重试参数
DB::transaction()方法默认在检测到死锁或超时异常时自动重试,其底层仅对QueryException类型的特定错误码触发重试逻辑。该机制通过重新创建事务会话并执行闭包内逻辑实现状态重置,确保每次重试均处于干净上下文。
1、在事务闭包后传入整数作为第二个参数,指定最大重试次数。
2、确保闭包内操作为幂等设计,避免因重复执行导致数据重复写入或计数错乱。
3、将可能引发死锁的更新顺序统一为固定表顺序(如始终先users后orders),降低并发冲突概率。
4、调用示例:DB::transaction(function () { DB::table('orders')->decrement('stock'); DB::table('inventory')->increment('available'); }, 3);
二、手动捕获死锁异常并重试
当需要对重试过程施加更精细控制(如记录日志、动态调整参数或引入退避延迟)时,可绕过DB::transaction()的自动机制,改用beginTransaction()配合显式rollback()和commit(),并在catch块中判断是否为死锁错误码后决定是否重试。
1、定义重试计数器变量并初始化为0。
2、使用while循环包裹事务逻辑,循环条件为计数器小于预设最大重试次数。
3、在循环体内调用DB::beginTransaction()开启事务。
4、执行业务SQL操作,若成功则调用DB::commit()并跳出循环。
5、若抛出PDOException,则检查$e->getCode()是否等于'40001'(PostgreSQL死锁)或'1213'(MySQL死锁),是则递增计数器并continue;否则重新抛出异常。
6、示例关键判断:if ($e->getCode() === '1213') { $retryCount++; continue; }
三、结合引用传递追踪重试次数
利用PHP闭包use语法的引用传递特性,可在事务闭包内部感知当前是第几次执行,从而实现差异化逻辑——例如首次尝试使用乐观锁版本号校验,后续重试改用强制覆盖策略,或在末次重试前添加日志告警。
1、在闭包外声明整型变量并以&符号传入use语句。
2、在闭包首行执行$attemptNumber++,使该变量随每次重试递增。
3、根据$attemptNumber值分支处理:首次尝试执行标准更新,第二次尝试增加WHERE条件中的version字段比对,第三次尝试跳过版本检查直接更新。
4、确保重试上限与DB::transaction()第二参数一致,防止超出预期次数。
5、关键传参写法:use (&$attemptNumber)
四、配置数据库连接层超时与隔离级别
从数据库驱动层减少死锁发生概率,属于前置防御手段。通过缩短事务持有锁的时间窗口及规范并发访问路径,可显著降低死锁触发率,从而减少对重试机制的依赖。
1、在config/database.php的mysql连接配置中添加'ssl_mode' => 'disabled'和'charset' => 'utf8mb4'以提升协议效率。
2、设置'timeout' => 30与'wait_timeout' => 60,避免长事务阻塞其他连接。
3、在事务开始前执行DB::statement("SET TRANSACTION ISOLATION LEVEL READ COMMITTED"),降低幻读与间隙锁竞争。
4、对高频更新字段建立覆盖索引,使UPDATE语句能走索引查找而非全表扫描,缩短行锁持有时间。
5、关键隔离级别设置:DB::statement("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")
五、使用队列异步化事务执行
将原本同步执行的高冲突事务移至队列中串行处理,从根本上消除并发写入同一资源的可能性。该方式不依赖重试,而是通过削峰填谷实现自然错峰,适用于订单创建、库存扣减等强一致性场景。
1、创建Job类并在handle()方法中封装完整事务逻辑。
2、在Job类中定义$tries = 3与$backoff = [1, 5, 15],实现指数退避重试。
3、分发任务时不直接执行,而是调用ProcessOrder::dispatch($orderData)推入队列。
4、确保queue:work进程单实例运行,或通过Redis锁保证同一订单ID的任务不会被多个worker同时消费。
5、关键分发语句:ProcessOrder::dispatch($orderData)->onQueue('high_priority')











