i have function in my controller that create a forecast :
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); }
As you can see i use updateOrCreate methods to add or update a forecast.
The problem is when 2 requests from the same user run at the same time (and no forecast is already created) 2 row are inserted.
Do you have a solution ? I See that the problem is not new but i could not find a solution https://github.com/laravel/framework/issues/19372
Advertisement
Answer
updateOrCreate
does 2 steps:
- tries to fetch the record
- depending on the outcome does an update or a create.
This operation is not atomic, meaning that between step 1 and 2 another process could create the record and you would end up with duplicates (your situation).
To solve your problem you need following:
- determine what columns would give the uniqueness of the record and add an unique index (probably compound between user_id, fixture_id, platform_id)
- you need to let database handle the upsert (
ON DUPLICATE KEY UPDATE
in MySQL,ON CONFLICT (...) DO UPDATE SET
in Postgres, etc). This can be achieved in Laravel by using theupsert(array $values, $uniqueBy, $update = null)
instead ofupdateOrCreate
.