0

阅读前需要注意的事项:

  • 我知道代码不是那么出色。请不要评论我的旧作品;)
  • 我知道 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 结构并将用户重定向到单独的页面,以防止多次提交

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

4

2 回答 2

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 代码时读取库存表

于 2013-08-22T01:23:19.537 回答
0

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

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

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

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

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

于 2013-08-21T23:35:01.580 回答