2012-09-16 51 views
2

我有一张桌子,上面有这个列:[user_id] [game_id] 我需要在两个玩家加入游戏时关闭游戏。 我用这个代码:Mysql插入冲突

if(mysql_num_rows(mysql_query("SELECT user_id FROM live_games WHERE game_id = '$gid'"))<2){ 
mysql_query("INSERT INTO live_games (user_id, game_id) VALUES ('$uid', '$gid')"); 
echo "You have joined the game"; 
}else{ 
echo "Table is full"; 
} 

代码将使只有两个玩家注册,但有些时候,也有在网站上的用户太多,这种情况将无法正常工作和三个用户将在表中添加。 我该如何解决?

+0

请不要使用'mysql_ *'函数,它已被弃用(请参阅[*红框*](http://php.net/manual/en/function.mysql-query.php))和容易受到sql注入的影响。使用[* PDO *](http://php.net/manual/en/book.pdo.php)或[* MySQLi *](http://php.net/manual/en/book.mysqli.php) 。 – alfasin

回答

4

您有几种选择:

  1. 如果使用InnoDB存储引擎,执行相同的事务INSERT语句中使用SELECT ... FOR UPDATE读锁定。

    使用PDO:

    $dbh = new PDO("mysql:dbname=$dbname;charset=utf8", $username, $password); 
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 
    
    $dbh->beginTransaction(); 
    $qry = $dbh->prepare(' 
        SELECT COUNT(*) FOR UPDATE FROM live_games WHERE game_id = ? 
    '); 
    $qry->execute([$gid]); 
    
    if ($qry->fetchColumn() < 2) { 
        $qry = $dbh->prepare(' 
        INSERT INTO live_games (user_id, game_id) VALUES (?, ?) 
        '); 
        $qry->execute([$uid, $gid]); 
        if ($qry->rowCount() && $dbh->commit()) echo 'You have joined the game'; 
    } else { 
        $dbh->rollBack(); 
        echo 'Table is full'; 
    } 
    
  2. 变化表结构,以便有两列,player1player2(最初NULL)并执行UPDATE live_games SET player2 = ? WHERE game_id = ? AND player2 IS NULL,然后检查受影响的行的数量;或

  3. 更改表结构,以便有一个额外的playerNumber列,然后通过(game_id, playerNumber)创建复合UNQUE索引。

+0

Wooow谢谢。没有PDO可以做到这一点吗? –

+0

@manfilsoof:你可以用mysqli来做。我不会尝试使用古老且即将被废弃的ext/mysql。 – eggyal

0

您正在运行两个查询以查看是否有人可以输入游戏。

完全有可能三个玩家同时点击加入,并且SELECT查询返回游戏(当前)有0个玩家;所以他们都被允许进入 - 更新然后踢,并阻止其他球员加入。

如果你想解决它,你需要找到另一种方法 - 你可以添加一个文件锁,所以一次只能运行一个SELECT查询,或者我相信将它包装在一个事务中也可以工作 - 这实质上是将两个查询都作为一个单独的实体运行。

+0

单独处于同一个事务中不会有任何区别,因为'SELECT'不会使记录在默认情况下被锁定。 – eggyal

+0

@eggyal - 有没有办法做到这一点与交易?我对他们不太确定。 – andrewsi

+0

是的,请参阅[我的答案](http://stackoverflow.com/a/12448086/623041)。 – eggyal

0

您需要LOCK TABLES声明附上您的疑问,像这样:

mysql_query("LOCK TABLES live_games WRITE"); 

if(mysql_num_rows(mysql_query("SELECT user_id FROM live_games WHERE game_id = '$gid'"))<2){ 
mysql_query("INSERT INTO live_games (user_id, game_id) VALUES ('$uid', '$gid')"); 
echo "You have joined the game"; 
}else{ 
echo "Table is full"; 
} 

mysql_query("UNLOCK TABLES"); 

,将执行到表按顺序访问,因此避免了不同用户在同一时间执行相同的代码,并发问题。

+0

会造成很多不必要的阻塞。 – eggyal

+0

这是一个很好的解决方案,但我在这张桌子上有几个打开的游戏,锁定桌子会影响其他玩家加入其他游戏(diffrent game_id's)。 –

+0

我可以使用特殊的game_id锁定行吗? –