使用MySQL和InnoDB防止并发访问和插入

时间:2013-09-26 16:52:13

标签: php mysql innodb

我目前正在使用以下规则制作即时赢奖游戏:第一个用户在最后一个获胜后至少半小时注册。因此用户可以每半小时赢一次。如果没有人在几个小时内注册,那么第一个注册胜利。

服务器使用PHP 5.2.0,这解释了我不使用DateTime类。我的表格是InnoDB。

我的脚本的这一部分在用户注册时由每个客户调用,并决定用户是否获胜:

$currentTime = date("Y-m-d H:i:s");

// We're looking for the last winner in the winners table
$res = $dbh->query("SELECT date FROM winnertable ORDER BY id DESC")->fetch(PDO::FETCH_ASSOC);

if(empty($res)){
    // If there is no last winner then the user wins
    $winner = true;
}else{
    // If there is already a last winner, we check the date of win, if superior to half an hour, the user wins 
    if(strtotime($currentTime)-strtotime($res["date"])>=1800){
        // Last winner was half an hour ago : winner
        $winner = true;
    }
}

// if the winner flag is set to true, add the user id to the winners table along with the date of winning
if($winner){
    $dbh->query("INSERT INTO winnertable (id_main, date) VALUES ('".$_SESSION['id']."', '".$currentTime."')");
}

下一页必须向用户显示结果,以便他知道他是否赢了。所以我事后无法做出CRON工作。

这个脚本存在缺陷,因为如果多个客户端在同一时间访问它,它可能会使多个客户端获胜。

我已经阅读了一些解决方案:

  • 使用锁:但我不知道性能成本。另外,我已经读过MyIsam表锁定了整个表,而InnoDB表只锁定了标记为更新的选定行。我不知道该怎么做。
  • 制作我的“日期”列UNIQUE应该可以防止同一日期的多次插入,并且似乎是一种优雅的解决方法,但它似乎有点太容易被信任,我真的很喜欢你的建议。

2 个答案:

答案 0 :(得分:0)

你可以做的是为这种情况增加一个阶段,你有另一个临时表来存储“潜在的”赢家 - 那些满足获胜标准的人。然后在其上运行SELECT作为按日期排序的辅助操作,索引按升序排列,限制为1。获取该值并将其添加到真正的获胜者表中,并将其用作下一次获胜计算的基础,并清除临时表。那样你应该只获得一个胜利者。它当然会增加开销,但它只会每半个小时发生一次,所以我认为这不应该是一个问题。

答案 1 :(得分:0)

我最终决定以不同的方式思考问题并决定发布解决方案,因为它可能对某些人有所帮助。

我已经阅读了InnoDB表行级别锁并执行了以下操作:

Stmt#1:UPDATE winnertable SET id_main = [id] WHERE date_add(next_date,INTERVAL X SECOND)<现在()和id_main = 0

如果PDO返回修改后的行($ stmt-> rowCount()),那么它就是胜利者,你必须在winnertable中为下一个获胜者插入一个新行:

Stmt#2(有条件的):INSERT INTO winnertable SET next_date = NOW()

感谢您的帮助!