Laravel 插入行重复,使用带 Race-Condition 的 updateOrCreate 方法
Laravel Row duplication inserted, with updateOrCreate method with Race-Condition
我的控制器中有创建预测的功能:
public function updateOrCreate(Request $request, $subdomain, $uuid)
{
$fixture = Fixture::where('uuid',$uuid)->firstOrFail();
request()->validate([
'local_team_score' => 'integer|min:0',
'visitor_team_score' => 'integer|min:0',
'winner_team_id' => 'integer|nullable'
]);
if ($fixture->status !== "PENDING"){
return response()->json([
'message' => "You can not add or modify a forecast if the fixture is not pending"
], 403);
}
$winner_team = null;
// local team win
if ($request->local_team_score > $request->visitor_team_score) {
$winner_team = $fixture->localTeam;
}elseif ($request->local_team_score < $request->visitor_team_score){ //visitor win
$winner_team = $fixture->visitorTeam;
}else{ // draw
$winner_team = FixtureTeam::where('team_id',$request->winner_team_id)->first();
}
$user = auth('api')->user();
$platform = Platform::first();
$forecast = Forecast::updateOrCreate([
'user_id' => $user->id,
'fixture_id' => $fixture->id,
'platform_id' => $platform->id
],[
'local_team_score' => $request->local_team_score,
'visitor_team_score' => $request->visitor_team_score,
'winner_team_id' => is_null($winner_team) ? null : $winner_team->team_id
]);
$forecast->load('winnerTeam');
return new ForecastResource($forecast);
}
如您所见,我使用 updateOrCreate 方法添加或更新预测。
问题是当同一用户 运行 同时发出 2 个请求时(并且尚未创建预测)插入 2 行。
你有解决办法吗?
我看到这个问题不是新问题,但我找不到解决方案 https://github.com/laravel/framework/issues/19372
updateOrCreate
执行 2 个步骤:
- 尝试获取记录
- 根据结果进行更新或创建。
此操作不是原子操作,这意味着在第 1 步和第 2 步之间,另一个进程可能会创建记录,而您最终会得到重复项(您的情况)。
要解决您的问题,您需要执行以下操作:
- 确定哪些列将提供记录的唯一性并添加唯一索引(可能是 user_id、fixture_id、platform_id 之间的复合)
- 您需要让数据库处理更新插入(
ON DUPLICATE KEY UPDATE
在 MySQL,ON CONFLICT (...) DO UPDATE SET
在 Postgres 等)。这可以在 Laravel 中通过使用 upsert(array $values, $uniqueBy, $update = null)
而不是 updateOrCreate
来实现。
我的控制器中有创建预测的功能:
public function updateOrCreate(Request $request, $subdomain, $uuid)
{
$fixture = Fixture::where('uuid',$uuid)->firstOrFail();
request()->validate([
'local_team_score' => 'integer|min:0',
'visitor_team_score' => 'integer|min:0',
'winner_team_id' => 'integer|nullable'
]);
if ($fixture->status !== "PENDING"){
return response()->json([
'message' => "You can not add or modify a forecast if the fixture is not pending"
], 403);
}
$winner_team = null;
// local team win
if ($request->local_team_score > $request->visitor_team_score) {
$winner_team = $fixture->localTeam;
}elseif ($request->local_team_score < $request->visitor_team_score){ //visitor win
$winner_team = $fixture->visitorTeam;
}else{ // draw
$winner_team = FixtureTeam::where('team_id',$request->winner_team_id)->first();
}
$user = auth('api')->user();
$platform = Platform::first();
$forecast = Forecast::updateOrCreate([
'user_id' => $user->id,
'fixture_id' => $fixture->id,
'platform_id' => $platform->id
],[
'local_team_score' => $request->local_team_score,
'visitor_team_score' => $request->visitor_team_score,
'winner_team_id' => is_null($winner_team) ? null : $winner_team->team_id
]);
$forecast->load('winnerTeam');
return new ForecastResource($forecast);
}
如您所见,我使用 updateOrCreate 方法添加或更新预测。
问题是当同一用户 运行 同时发出 2 个请求时(并且尚未创建预测)插入 2 行。
你有解决办法吗? 我看到这个问题不是新问题,但我找不到解决方案 https://github.com/laravel/framework/issues/19372
updateOrCreate
执行 2 个步骤:
- 尝试获取记录
- 根据结果进行更新或创建。
此操作不是原子操作,这意味着在第 1 步和第 2 步之间,另一个进程可能会创建记录,而您最终会得到重复项(您的情况)。
要解决您的问题,您需要执行以下操作:
- 确定哪些列将提供记录的唯一性并添加唯一索引(可能是 user_id、fixture_id、platform_id 之间的复合)
- 您需要让数据库处理更新插入(
ON DUPLICATE KEY UPDATE
在 MySQL,ON CONFLICT (...) DO UPDATE SET
在 Postgres 等)。这可以在 Laravel 中通过使用upsert(array $values, $uniqueBy, $update = null)
而不是updateOrCreate
来实现。