快速刷新一个 php 获取多个查询


Fast refreshing a php get with multiple queries

阅读前需要注意的事项:

  • 我知道代码不是那么出色。 请不要评论我的旧作品;)
  • 我知道mysql_query已被弃用。目前更新它不在这个问题的范围内

问题背景

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

页面很简单。 在原始加载中,在循环访问对数据库的 mysql 查询后会显示一个表。 其中每一行都显示一个链接,其中包含:

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

XXX 与数据库中items table中的项的 ID 相关。 确认=0 具有以下代码:

if(isset($_GET['use'])){
  $id=@mysql_real_escape_string($_GET['use']);
    if(isset($_GET['confirm'])){
      $confirm=@mysql_real_escape_string($_GET['confirm']);
      if($confirm==0){
       // show a confirm button of YES / NO for them 
       // to click which has 1 for confirm

然后,用户可以单击"是",将其转移到:

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;
        $result=@mysql_query("SELECT * FROM inventory WHERE item_id=$id AND u_id=$user_id");
        $num_rows=@mysql_num_rows($result);
        $r=@mysql_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();
        }
        $result=@mysql_query("SELECT * FROM items WHERE id=$id");
        $r=@mysql_fetch_array($result);
        $type=$r['type'];
        $item_name=$r['item_name'];

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

此时之后的代码从数据库中删除项目的数量并实现项目的"效果"。 假设执行了更新

问题:我在这里遇到的实际问题是,如果用户多次刷新页面,他们实际上可以执行update查询,但他们实际上可以跳过对数量的检查。 更新查询会一遍又一遍地运行,但由于没有错误消息,因此对数量的检查永远不会运行多次。 今天的一个例子是,当我的库存中有 3 件商品时,我按了大约 100 次 f5。 我设法让查询更新运行 16 次,而没有显示任何错误消息。 如果我等待几秒钟并再次按 f5,它将显示一条错误消息,指出我没有任何这些项目。

以下解决方案不是一种选择,因为我不想浪费时间编码:

  • 创建 ajax 调用以防止在处理所有查询之前进行多次提交。
  • 实现 MVC 结构并将用户重定向到单独的页面,以防止多次提交

如果有人可以解释此错误的原因(使用相关阅读材料(,甚至提供解决它的解决方案,那就太好了!谢谢!

由于在数据库中查询库存水平与后续更新以降低库存水平之间的时间,您存在争用条件。如果您非常快速地发送多个请求,则在第一个请求有时间更新库存水平之前,每个请求都将收到相同的库存水平(在本例中为 3(。

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

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

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

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

该问题可能是由于在 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 代码