2012-09-16 90 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鎖定行嗎? –