Skip to content
Advertisement

Laravel Row duplication inserted, with updateOrCreate method with Race-Condition

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:

  1. tries to fetch the record
  2. 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:

  1. determine what columns would give the uniqueness of the record and add an unique index (probably compound between user_id, fixture_id, platform_id)
  2. 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 the upsert(array $values, $uniqueBy, $update = null) instead of updateOrCreate.
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement