0

読む前の注意事項:

  • コードがそれほど素晴らしいものではないことは承知しています。私の古い作品にコメントしないでください;)
  • mysql_query が非推奨であることは承知しています。現時点での更新は、この質問の範囲内ではありません

質問の背景

今日、古い Web サイトから興味深いバグ レポートを受け取りました。このバグが発生するとは予想していなかったので、非常に心配しています。

ページはシンプルです。元のロードでは、データベースへの mysql クエリをループした後にテーブルが表示されます。これらの各行には、次のリンクが表示されます。

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

XXXitems tableは、データベース内の項目の ID に関連しています。confirm=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 つのアイテムがあり、f5 を約 100 回押したときです。エラーメッセージが表示されることなく、クエリの更新を16回実行することができました。数秒待ってからもう一度 f5 キーを押すと、それらのアイテムがないというエラー メッセージが表示されます。

コーディングに時間を無駄にしたくないので、次の解決策はオプションではありません。

  • すべてのクエリが処理される前に複数の送信を防ぐために ajax 呼び出しを作成します。
  • MVC 構造を実装し、複数の送信を防ぐ別のページにユーザーをリダイレクトする

誰かがこのバグの理由を (関連する読み物で) 説明したり、それを解決するための解決策を提供したりできれば、それは素晴らしいことです! ありがとう!

4

2 に答える 2

0

この問題は、Web サーバーで複数の同時スレッドが実行されており、非ブロッキング/非トランザクション データベース操作の要求に同時に応答していることが原因である可能性があります。リクエストの一部は在庫数量チェックに合格する場合がありますが、他のリクエストはまだ処理中です。

考えられる解決策の 1 つは、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。つまり、ギャップがないようにします。

考えられる解決策の 1 つは、在庫レベル > 0 で更新を試み、影響を受ける行数を確認することです。

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

影響を受ける行数が 0 の場合、在庫がありません。影響を受ける行数が 1 の場合、在庫がありました。複数のクエリを実行すると在庫がゼロになり、その時点でいくつかのエラー メッセージが表示されます。

于 2013-08-21T23:35:01.580 に答える