2013-08-21 95 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的代碼