天天看點

商品庫存的扣除過程,如何防止超賣?

在商品購買的過程中,庫存的抵扣過程,一般操作如下:

1、select根據商品id查詢商品的庫存。

2、根據下單的數量,計算庫存是否足夠,如果存庫不足則抛出庫存不足的異常,如果庫存足夠,則減去扣除的庫存得到最新的庫存剩餘值。

3、set設定最新的庫存剩餘值。

上述過程的僞代碼如下:

// 根據商品id擷取商品剩餘庫存
select stock_remaing from stock_table where id=${goodsId};

// 操作庫存
// 比較庫存
if(stock_remaing <quantity){
   // 抛出庫存不足的異常
}
else{
  // 抵扣以後的庫存值
  int new_stock=stock_remaing - quantity;
}

// 根據商品id設定計算後的庫存
update stock_table  set stock_remaing =${new_stock} id=${goodsId};      

并發修改資料庫存超賣

如果資料庫事務的隔離級别不是串行化(serializable),根據事務的特性,在并發修改的時候,可能會出現寫覆寫的問題。

假設,商品的剩餘庫存stock_remaing 為100,客戶A下單20,客戶B下單30,在并發扣庫存的時候,可能存在超賣。如果客戶A和客戶B同時擷取剩餘庫存為100,則會出現事務後送出的值會覆寫前一個客戶送出的值,有可能剩餘的庫存是80或者70。

流程如下:

商品庫存的扣除過程,如何防止超賣?

加鎖更新存庫

為了在事務控制中,防止寫覆寫,你會想到使用select for update的方式,将該商品的庫存鎖住,然後執行餘下的操作。

商品庫存的扣除過程,如何防止超賣?

以上,使用悲觀鎖方式,在分布式服務中,如果并發情況比較高的時候,扣減庫存的操作是串行操作,效率很低。

使用樂觀鎖的方式更新

在更新的時候,使用(CAS+版本号更新)+重試條件(重試次數或者重試時間限制)樂觀鎖的方式更新庫存。此時,如果,客戶A和客戶B同時讀取到庫存剩餘100,在更新的時候,有一個操作會失敗。

商品庫存的扣除過程,如何防止超賣?

該種方式可以大大提高并發性,也可以保證資料的一緻性;通過重試次數和重試時間的條件控制,可以防止過多的重試帶來的資料庫壓力。

可以使用直接遞減的方式執行麼?

在抵扣庫存的時候,有的人提議不執行select,計算,set三段式的操作,直接扣減的方式,并且對于扣減到小于零的情況作了判斷。僞代碼如下:

update stock_table set remaing_stock=remaing_stock-${quantity} 
where id =商品id
and remaing_stock>${quantity};      

在分布式服務調用中,因為網絡異常,擷取伺服器異常,可能在微服務調用時,存在服務重試。例如,場景的網關逾時,服務重試機制。此時,該種方式不滿足幂等性,而存在多扣的情況。例如,同一使用者扣減庫存時,服務重試,極端情況下,該使用者扣減庫存操作執行多次,則就出現了商品超賣。

可以使用redis進行庫存的抵扣麼?

由于沒有研究過redis源碼,對于這種方式參考了大牛的回複,答案是可以使用redis的事務性扣減餘額,但在CAS機制上比mysql沒有優勢,高性能是因為其記憶體存儲的原因,帶來的副作用是資料有丢失風險。

原文連結:

https://blog.csdn.net/new_com/article/details/105568124

版權聲明:本文為CSDN部落客「iloveoverfly」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。