2013-08-21 29 views
0

事情读数前要注意:快速刷新与多个查询一个PHP得到

  • 我知道的代码是不是辉煌。请不要评论我的旧作;)
  • 我知道mysql_query已被弃用。更新此刻是不是这个问题的范围内

问题背景

我通过老的网站今天得到了一个有趣的bug报告已引起了我一个巨大的关注量,因为我没想到会发生这个错误。

该页面很简单。在原始加载中,在将mysql查询循环到数据库之后,会显示一个表。每个那些行的显示与链接:

url.com/items.php?use=XXX&confirm=0 

到该项目的ID中items table在数据库中的XXX涉及。该确认= 0有以下代码:

if(isset($_GET['use'])){ 

    [email protected]_real_escape_string($_GET['use']); 

    if(isset($_GET['confirm'])){ 

     [email protected]_real_escape_string($_GET['confirm']); 

     if($confirm==0){ 

     // show a confirm button of YES/NO for them 
     // to click which has 1 for confirm 

然后,用户可以在YES点击哪个它们转移到:

url.com/items.php?use=XXX&confirm=1 

然后,该代码从上面的代码,其执行以下操作进行到一个else检查:

if($id<1){ 
      echo "<p class='error-message'>An error has occurred.</p>"; 
      print "<p class='center'><a href='http://www.url.com/items.php'>[Back]</a></p>"; 
      include("inc/ftr.php"); 
      exit(); 
     } 

     if(empty($id)){ 
      echo "<p class='error-message'>An error has occurred.</p>"; 
      print "<p class='center'><a href='http://www.url.com/items.php'>[Back]</a></p>"; 
      include("inc/ftr.php"); 
      exit(); 
     } 

     $quantity = 0; 
     [email protected]_query("SELECT * FROM inventory WHERE item_id=$id AND u_id=$user_id"); 
     [email protected]_num_rows($result); 
     $r[email protected]_fetch_array($result); 
     $quantity=$r['quantity']; 

     if($num_rows==0){ 
      echo "<p class='error-message'>You do not own any of these.</p>"; 
      print "<p class='center'><a href='http://www.url.com/items.php'>[Back]</a></p>"; 
      include("inc/ftr.php"); 
      exit(); 
     } 

     if($quantity<1){ 
      echo "<p class='error-message'>You don't have any of these left!</p>"; 
      print "<p class='center'><a href='http://www.url.com/items.php'>[Back]</a></p>"; 
      include("inc/ftr.php"); 
      exit(); 
     } 

     [email protected]_query("SELECT * FROM items WHERE id=$id"); 
     [email protected]_fetch_array($result); 
     $type=$r['type']; 
     $item_name=$r['item_name']; 

以上执行相关检查以确保ID存在,然后查询数据库以从库存中获取当前数量并检查它不低于0.如果它低于0,那么它会在那一点阻止页面。

此点后的代码从数据库中删除项目的数量并实现项目的“效果”。我们假设执行更新

问题: 我有这里的实际问题是,如果用户刷新页面多次,他们实际上可以得到update查询来执行,但他们其实可以跳过数量的检查。更新查询反复运行,但由于没有错误消息,所以对数量的检查永远不会运行多次。今天的例子是当我在我的库存中有3项,并且我按了f5约100次。我设法让查询更新运行16次而不显示任何错误消息。如果我等了几秒钟并再次按f5,它会显示一条错误消息,说我没有任何这些项目。

了以下解决方案不是我不想浪费时间编码的选项:

  • 创建Ajax调用,以防止多次提交的所有查询都被处理了。
  • 实现了MVC结构,将用户重定向到一个单独的页面,防止多次提交

如果有人可以解释这个错误的原因(相关阅读材料),甚至提供了一个解决方案来解决它会太好了!谢谢!

回答

0

由于查询数据库的库存水平与后续更新以减少库存水平之间的时间有关,所以存在竞争条件。如果您发送多个请求的速度非常快,那么在第一个请求有时间更新库存水平之前,每个请求都会收到相同的库存水平(本例中为3)。

您需要更改您的代码,以便您的query & decrement为原子 - 即没有间隙。

一个可能的解决方案是尝试更新,其中库存水平> 0并查看有多少行受到影响。

UPDATE products set `stockLevel`=`stocklevel`-1 where `productId` = 'something' and `stocklevel`>0 

如果受影响的行数为0,则表示没有库存。如果受影响的行数是1,那么你有库存。多个查询会将库存减少到零,此时您应该看到一些错误消息。

0

这个问题很可能是由于在Web服务器上运行了多个并发线程,同时响应请求以进行非阻塞/非事务性数据库操作。有些请求可能会通过库存数量检查,而其他请求仍在处理中。

一个可能的解决方案是使用MySQL事务,但这可能需要迁移到mysqli或PDO,这似乎超出了您期望的解决方案的范围,并且需要您可能没有的InnoDB表。

如果你曾经选择升级到使用mysqli的,这里是一些有用的信息:

http://dev.mysql.com/doc/refman/5.0/en/commit.html

http://coders-view.blogspot.com/2012/03/how-to-use-mysql-transactions-with-php.html

另一种解决办法是实施 “锁定” 的功能。 http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html

mysql_query("LOCK TABLES inventory WRITE;"); 
// all your other PHP/SQL here 
mysql_query("UNLOCK TABLES;"); 

这将阻止其它客户端读取库存表,而第一个客户端仍忙于处理您的PHP/MySQL的代码