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. 根据结果进行更新或创建。
    此操作不是原子操作,这意味着在第 1 步和第 2 步之间,另一个进程可能会创建记录,而您最终会得到重复项(您的情况)。

要解决您的问题,您需要执行以下操作:

  1. 确定哪些列将提供记录的唯一性并添加唯一索引(可能是 user_id、fixture_id、platform_id 之间的复合)
  2. 您需要让数据库处理更新插入(ON DUPLICATE KEY UPDATE 在 MySQL,ON CONFLICT (...) DO UPDATE SET 在 Postgres 等)。这可以在 Laravel 中通过使用 upsert(array $values, $uniqueBy, $update = null) 而不是 updateOrCreate 来实现。