本文目录导读:

在 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 () {
// 数据库操作
});
注意事项
- 不要过度重试:建议最多重试 3-5 次
- 使用指数退避:每次重试间隔时间递增
- 记录日志:记录重试情况便于排查问题
- 幂等设计:确保重试操作是幂等的
- 监控告警:监控死锁频率,及时发现系统问题
选择哪种方式取决于你的具体需求,对于大多数情况,使用 Laravel 内置的 DB::transaction() 方法并指定重试次数就足够了。