在商品購買的過程中,庫存的抵扣過程,一般操作如下:
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版權協定,轉載請附上原文出處連結及本聲明。