у меня есть rounds
таблица mysql (innodb), используемая для отслеживания всех игр, в которые играет данный пользователь. Он имеет следующие столбцы: id (int), userId (int), gameId (int), status (int).
Id — это первичный ключ таблицы, userId и gameId представляют внешние ключи для других таблиц, а в состоянии значения int представляют 3 различных состояния, если игровой раунд — в процессе, завершен, ошибка.
Один пользователь не может перейти к новому раунду игры, пока его предыдущий раунд еще не начался.
Мой код делает следующее:
SELECT id FROM rounds WHERE userId = <user> AND gameId = <game> AND status = <in progress> LIMIT 1
Если выбор возвращает результат, выдается ошибка. В противном случае я создаю новый раунд:
INSERT INTO rounds (userId, gameId, status) VALUES (<user>, <game>, <in progress>)
;
Между выделением и вставкой нет другого кода, и в большинстве случаев кажется, что все работает правильно и создает новый раунд игры, в то время как предыдущий процесс еще продолжается, что приводит к ошибке.
Но когда я тестирую код на предмет параллелизма и производительности при большой нагрузке, есть несколько раундов, которые удается вставить одновременно. (Я использую простой скрипт узла, который отправляет 200 запросов асинхронно.)
У вас есть идеи, как мне изменить код, чтобы во всех случаях у меня был максимум 1 активный раунд?
Я, кажется, застрял в этой проблеме, и я знаю, что должно быть простое решение купить, я просто не могу видеть это. 🙁
Любая помощь очень ценится!
PS: я пытался использовать INSERT ... INTO rounds SELECT ... FROM ROUNDS WHERE NOT EXISTS (SELECT id FROM rounds WHERE userId=<user> AND gameId=<game> AND status=<in progress>)
но это приводит к тупикам ..
РЕДАКТИРОВАТЬ:
Код выглядит примерно так:
В игре:
public function play() {
$gamesInProgress = $this->repo->getGamesInProgress($this->user->getId(), $this->id, self::STATUS_IN_PROGRESS);
if($gamesInProgress) throw \Exception('Only one active game is allowed.');
$this->createGame();
// some other code
}
В репо:
public function checkForGamesInProgress($userId, $gameId, $status) {
$stmt = $this->dbh->prepare('SELECT `id` FROM `rounds` WHERE `userId`=:userId AND `gameId`=:gameId AND `status`=:status LIMIT 1');
$stmt->prepare([
'userId' => $userId,
'gameId' => $gameId,
'status' => $status
]);
return $stmt->fetchColumn();
}
Хорошей практикой будет использование «составного ключа». Таким образом, база данных гарантировала, что дублированные строки не будут вставлены. Но тогда вам придется обработать ошибку базы данных, когда (случайно) попытаетесь вставить дублированную запись.
Я знаю, что вы пробовали это, но что происходит, когда вы запускаете это?
INSERT INTO rounds (userId, gameId, status)
SELECT <user>, <game>, '<in progress>' FROM rounds WHERE NOT EXISTS(
SELECT id FROM rounds WHERE userId = <user> AND gameId = <game> AND status = '<in progress>' LIMIT 1)
Других решений пока нет …