mysql處理高並發,防止庫存超賣


庫存超賣的問題作描述:一般電子商務網站都會遇到如團購、秒殺、特價之類的活動,而這樣的活動有一個共同的特點就是訪問量激增、上千甚至上萬人搶購一個商品。然而,作為活動商品,庫存肯定是很有限的,如何控制庫存不讓出現超買,以防止造成不必要的損失是眾多電子商務網站程序員頭疼的問題,這同時也是最基本的問題。
從技術方面剖析,很多人肯定會想到事務,但是事務是控制庫存超賣的必要條件,但不是充分必要條件。
舉例:
總庫存:4個商品
請求人:a、1個商品 b、2個商品 c、3個商品
程序如下:

beginTranse(開啟事務)
try{
    $result = $dbca->query('select amount from s_store where postID = 12345');
    if(result->amount > 0){
        //quantity為請求減掉的庫存數量
        $dbca->query('update s_store set amount = amount - quantity where postID = 12345');
    }
}catch($e Exception){
    rollBack(回滾)
}
commit(提交事務)

  


以上代碼就是我們平時控制庫存寫的代碼了,大多數人都會這么寫,看似問題不大,其實隱藏着巨大的漏洞。數據庫的訪問其實就是對磁盤文件的訪問,數據庫中的表其實就是保存在磁盤上的一個個文件,甚至一個文件包含了多張表。例如由於高並發,當前有三個用戶a、b、c三個用戶進入到了這個事務中,這個時候會產生一個共享鎖,所以在select的時候,這三個用戶查到的庫存數量都是4個,同時還要注意,mysql innodb查到的結果是有版本控制的,再其他用戶更新沒有commit之前(也就是沒有產生新版本之前),當前用戶查到的結果依然是舊版本;

然后是update,假如這三個用戶同時到達update這里,這個時候update更新語句會把並發串行化,也就是給同時到達這里的是三個用戶排個序,一個一個執行,並生成排他鎖,在當前這個update語句commit之前,其他用戶等待執行,commit后,生成新的版本;這樣執行完后,庫存肯定為負數了。但是根據以上描述,我們修改一下代碼就不會出現超買現象了,代碼如下:


beginTranse(開啟事務)
try{
    //quantity為請求減掉的庫存數量
    $dbca->query('update s_store set amount = amount - quantity where postID = 12345');
    $result = $dbca->query('select amount from s_store where postID = 12345');
    if(result->amount < 0){
       throw new Exception('庫存不足');
    }
}catch($e Exception){
    rollBack(回滾)
}
commit(提交事務)
或者

就是在update時,代入之前的庫存作為條件,如:
update stocks set count=count-1 where count=20 and productid=xxxx

這樣,在並發時,如果存在多條同時update,那么只有一條是成功的,其他的不會寫入數據,也不需要回滾,只需要獲得是否update成功的信息,就轉去相應的處理界面就可以了。

1.用額外的單進程處理一個隊列,下單請求放到隊列里,一個個處理,就不會有並發的問題了,但是要額外的后台進程以及延遲問題,不予考慮。

2.數據庫樂觀鎖,大致的意思是先查詢庫存,然后立馬將庫存+1,然后訂單生成后,在更新庫存前再查詢一次庫存,看看跟預期的庫存數量是否保持一致,不一致就回滾,提示用戶庫存不足。

3.根據update結果來判斷,我們可以在sql2的時候加一個判斷條件update ... where 庫存>0,如果返回false,則說明庫存不足,並回滾事務。

4.借助文件排他鎖,在處理下單請求的時候,用flock鎖定一個文件,如果鎖定失敗說明有其他訂單正在處理,此時要么等待要么直接提示用戶"服務器繁忙"

本文要說的是第4種方案,大致代碼如下: 

我后來又想了一種方式,如果網站流量非常大的話,直接操作mysql是有問題的,就需要針對高並發做優化:
1、庫存存入緩存服務器,下面以redis為例;
2、訂單的生成也存入redis,訂單號可以根據年月日時分秒+產品id+userid來保證唯一性;
3、利用redis的INCRBY來處理
redis 127.0.0.1:6379> set number 109
OK
redis 127.0.0.1:6379> get number
"109"
redis 127.0.0.1:6379> INCRBY number 20
(integer) 129
redis 127.0.0.1:6379> INCRBY number 20
(integer) 149
redis 127.0.0.1:6379> INCRBY number 21
(integer) 170
redis 127.0.0.1:6379> INCRBY number -21
(integer) 149
redis 127.0.0.1:6379>

這樣當number小於等於0的時候,就說明庫存沒有了;

不知道這種方式有沒有問題,沒有具體實踐過,如果有知道的朋友可以留言交流一下!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM