Laravel死锁重试怎么配置

wen PHP项目 19

本文目录导读:

Laravel死锁重试怎么配置

  1. 使用 Laravel 内置重试机制
  2. 代码层面手动重试
  3. 使用 Laravel 的重试中间件
  4. 使用第三方包
  5. 最佳实践建议
  6. 注意事项

在 Laravel 中配置死锁重试,主要有以下几种方式:

使用 Laravel 内置重试机制

数据库配置方式(推荐)

config/database.php 中配置:

'mysql' => [
    'driver' => 'mysql',
    // ... 其他配置
    'options' => [
        PDO::ATTR_EMULATE_PREPARES => true,
    ],
    'retry_after' => 100, // 毫秒
    'max_retries' => 3,   // 最大重试次数
],

队列任务重试

对于队列任务,在 config/queue.php 中配置:

'connections' => [
    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90,
        'after_commit' => true,
    ],
],

代码层面手动重试

使用 DB::transaction() 方法

use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;
try {
    $result = DB::transaction(function () {
        // 你的数据库操作
        DB::table('users')->where('id', 1)->update(['name' => 'New Name']);
        DB::table('orders')->insert([...]);
        return $result;
    }, 3); // 第三个参数是重试次数
} catch (QueryException $e) {
    if ($e->getCode() == '40001' || str_contains($e->getMessage(), 'deadlock')) {
        // 处理死锁异常
    }
}

自定义重试逻辑

use Illuminate\Support\Facades\DB;
function retryOnDeadlock(callable $callback, int $maxAttempts = 3, int $delay = 100)
{
    $attempts = 0;
    while ($attempts < $maxAttempts) {
        try {
            return DB::transaction($callback);
        } catch (\Illuminate\Database\QueryException $e) {
            $attempts++;
            // 检查是否是死锁
            if ($this->isDeadlock($e)) {
                if ($attempts >= $maxAttempts) {
                    throw $e;
                }
                usleep($delay * 1000); // 转换为微秒
                continue;
            }
            throw $e;
        }
    }
}
private function isDeadlock(QueryException $e)
{
    $message = $e->getMessage();
    return str_contains($message, 'Deadlock') || 
           str_contains($message, 'deadlock') ||
           $e->getCode() == '40001' ||
           $e->getCode() == '1213';
}
// 使用
retryOnDeadlock(function () {
    // 数据库操作
});

使用 Laravel 的重试中间件

创建重试中间件

namespace App\Http\Middleware;
use Closure;
use Illuminate\Database\QueryException;
class RetryOnDeadlock
{
    public function handle($request, Closure $next, $maxRetries = 3, $delay = 100)
    {
        $attempts = 0;
        do {
            try {
                return $next($request);
            } catch (QueryException $e) {
                $attempts++;
                if ($attempts >= $maxRetries) {
                    throw $e;
                }
                if ($this->isDeadlock($e)) {
                    usleep($delay * 1000);
                    continue;
                }
                throw $e;
            }
        } while (true);
    }
    private function isDeadlock($e)
    {
        return str_contains($e->getMessage(), 'Deadlock') || 
               $e->getCode() == '1213';
    }
}

注册中间件:

// app/Http/Kernel.php
protected $routeMiddleware = [
    'retry' => \App\Http\Middleware\RetryOnDeadlock::class,
];
// 在路由中使用
Route::post('/update-data', function () {
    // 数据库操作
})->middleware('retry:3,200');

使用第三方包

laravel-db-deadlock-retry

composer require beyondcode/laravel-db-deadlock-retry

安装后自动启用,无需额外配置。

最佳实践建议

完整的重试策略示例

use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;
class DatabaseService
{
    const MAX_RETRIES = 3;
    const BASE_DELAY = 100; // 毫秒
    public function executeWithRetry(callable $callback)
    {
        $attempts = 0;
        while (true) {
            try {
                return DB::transaction($callback);
            } catch (QueryException $e) {
                $attempts++;
                if (!$this->isDeadlock($e) || $attempts >= self::MAX_RETRIES) {
                    throw $e;
                }
                // 指数退避
                $delay = self::BASE_DELAY * pow(2, $attempts - 1);
                usleep($delay * 1000);
                // 记录重试日志
                \Log::warning('数据库死锁,正在重试', [
                    'attempt' => $attempts,
                    'delay' => $delay,
                ]);
            }
        }
    }
    private function isDeadlock(QueryException $e)
    {
        return in_array($e->getCode(), ['40001', '1213']) || 
               preg_match('/deadlock/i', $e->getMessage());
    }
}

使用:

$service = new DatabaseService();
$result = $service->executeWithRetry(function () {
    // 数据库操作
});

注意事项

  1. 不要过度重试:建议最多重试 3-5 次
  2. 使用指数退避:每次重试间隔时间递增
  3. 记录日志:记录重试情况便于排查问题
  4. 幂等设计:确保重试操作是幂等的
  5. 监控告警:监控死锁频率,及时发现系统问题

选择哪种方式取决于你的具体需求,对于大多数情况,使用 Laravel 内置的 DB::transaction() 方法并指定重试次数就足够了。

抱歉,评论功能暂时关闭!