**設計的過程中,必然會考慮到乙個庫存扣除的問題,超賣將會帶來一定的損失和麻煩,在某乙個時間段的瞬間的流量會造成庫存的併發性操作帶來庫存超額扣除。針對這類的問題,我先來分析原因吧。
只有弄清楚為什麼會被超賣才能解決超賣問題,這裡我們要弄清楚mysql的預設的扣除方式,update 表 store=store-1 where id=指定的id
首先我們看這樣的語句有哪些問題呢?
庫存有可能扣到為負數,無法鎖定最後的庫存
如果從外面select一次庫存,再查詢又怕出現主從不一致問題
併發問題
如何解決以下問題呢?
1、採用redis佇列的方式進行庫存增減運算。將建立訂單、退款、取消訂單做list的相應的增減操作
(1)建立商品、變更數量
初始化redis的list結構,變更商品數量的時候,redis的list數量做相應的增減。
(2)商品上架、下架
下架:設定redis的list過期
上架:初始化redis的list
(3)建立訂單成功
list進行出佇列的lpop
(4)取消訂單、退款
list的lpush入佇列,進行補償
(5)庫存校驗
判斷list的數量-當前的購買的數量 > 0的情況
否則:提示庫存不足
這種方案,看起來很完美,沒啥問題,細心下你會發現,假如一些使用者進入佇列後,他不想買了,停在頁面時間較長,這個就比較不好處理了,直接性導致很多刃餘庫存無法釋放,估計還得寫lua來控制了,比較得不償失。
2、 鎖定最後庫存+樂觀鎖
系統中通過事務來判斷,保證扣減後資料不為負數,否則回滾
設定資料庫字段資料為無符號證書,通過扣減是,字段值小於零觸發sql語句報錯
select id,store,status,version from 商品表 where id=#1 and status=1,讀出庫存以及版本
修改庫存:update 商品表 set store=store-#1,version=version+1 where status=1 and version=#version and store - #1 》 0
讀取庫存和版本資訊的時候可強行讀主庫,避免主從不一致的情況,版本資訊,
判斷:庫存-num > 0,版本=當前版本,避免延遲。
缺點:這種方案也是網上最多的方案,看起來很完美的,實際上也是有缺陷的,假如你的介面事務開始到事務結束,所需的時間是200ms,在這個200ms內的所有併發操作,只有乙個人是成功的,其它使用者因為樂觀鎖的原因都是失敗的。當日了這個是針對併發量較大的情況才會發生的,一般的小流量倒是可以對付的。
3、 鎖定最後庫存無樂觀鎖
系統中通過事務來判斷,保證扣減後資料不為負數,否則回滾
反正我不管最終update多少次,只要扣除成功了就行。也就是說我守住最後的底線,這種情況就不會出現乙個人成功,其他人都失敗的情況。事務過程中實際也是重新整理髒頁的過程,這種過程主庫的update操作實際都是內部有個排隊機制的,不會出現超賣的現象。
update 商品表 set store=store-#1 where status=1 and store - #1 》 0
個人認為3方案更加人性化一些,也比較簡單,不需要讀取過多的資訊
1方案成本較高一些,當然了大型專案比較適合
2方案比較嚴謹一些,避免出現問題的機率較大,大型公司的mysql伺服器在這個中間事務開始到操作結束,10ms不到,這種情況下一人成功,其他人失敗問題不大。
庫存超賣的解決方案
update sku info set kc kc 1 where sku id and kc 0 在高併發下,多人搶同一庫存,由於資料庫讀寫可以並行執行的原因,會導致修改庫存時,庫存不足出現超賣。悲觀鎖解決 在select加乙個行鎖,與更新庫存操作互斥,保證查詢庫存時,庫存不被修改 在查詢和更新庫...
超賣現象之解決方案
行級鎖定是目前各大資料庫管理軟體所實現的鎖定顆粒度最小的,所以發生鎖定資源爭用的概率也最小,能夠給予應用程式盡可能大的併發處理能力而提高一些需要高併發應用系統的整體效能。但是由於鎖定資源的顆粒度很小,所以每次獲取鎖和釋放鎖消耗的資源也更多,帶來的消耗自然也就更大了。此外,行級鎖定也最容易發生死鎖。1...
mysql 樂觀鎖 超賣 秒殺超賣解決方案
方案一 redis事務處理 multi 我們可以使用redis中的監聽 watch 方法,去監聽庫存數量,一旦庫存數量在其他客戶端發生改變,後續操作則會失敗。watch key1 key2 監聽key1 key2有沒有變化,如果有變,則事務取消 方案二 redis分布式鎖 分布式鎖確保只有乙個執行緒...