插入表

时间:2017-03-14 23:13:03

标签: php mysql concurrency race-condition

我有一个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

如果select返回结果,则抛出错误。否则我正在创造新一轮: INSERT INTO rounds (userId, gameId, status) VALUES (<user>, <game>, <in progress>);

select和insert之间没有其他代码,并且大多数时候一切似乎都正常工作并创建新的游戏回合,而前一个仍在进行中,导致错误。 但是当我在大负载下测试并发性和性能的代码时,有些轮可以同时插入。 (我使用的是简单的节点脚本,它可以异步发送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();
}

1 个答案:

答案 0 :(得分:0)

使用“复合键”是一个好习惯。这样,数据库确保不插入重复的行。但是,当(意外地)尝试插入重复的条目时,您将不得不处理数据库错误。

我知道你已经尝试过了,但是当你运行它时会发生什么?

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)