商城商品超賣處理


首先環境介紹下:商城商品可能存在幾個端(PC、APP),其次每個端對應的服務端又可能做了負載均衡(即也有多個服務端)。

要實現的目標和功能:保證商品不會出現超賣的情況。超賣商品后,無法對商品進行發貨,是一種不負責任的行為。

方案實現討論流程

“要實現不超賣,首先商品庫存的扣減不能使用框架進行更新,因為框架是設置值,如果在這段時間,又有人購買了,則商品庫存必然會出現問題。要采用手寫SQL方式。並且sql中還要判斷是否大於等於指定的購買量。”

UPDATE `SKU_Info`  SET skuNum=skuNum-1000 WHERE id='00293cb7-d8cf-4470-a66d-bb45ca2b130000293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000;

“要實現不超賣,我們可以對方法加上同步鎖,這樣可以解決”。

“方法加上同步鎖后,用戶下單將會出現排隊的情況,性能有問題。”

“那我們可以實現對同一商品進行加鎖,這樣可以解決購買不同商品不會相互阻塞。如果有包含關系,也應該加鎖。比如A用戶購買商品1和商品2,B用戶購買商品1,因為他們都有商品1,則應該加鎖。”

“你這個方案應該可以解決問題,采用分布式鎖的方式可以解決,我們可以使用redis來做。”

“是的,確實可以解決問題,並且多個服務端也不存在問題了,就這么干。”

“我們可以對訂單中的所有商品的sku值進行排序,拼接成一個skuId值,然后MD5的值作為key,其它訂單進來方法時,按同樣的操作進行檢測是否正在下單,如果是,則等待。”

“你這種方案忽略了商品不同的情況,就比如上面的例子中,A購買商品1和商品2,B購買商品1,那么他們的key是不同的,因而達不到效果。”

“我們可以對每個商品sku的id定義個鎖,這樣每次購買時,我們針對每個商品進行檢測,這樣就可以了,絕對能夠保證同步。”

“這種方法可行,不過還是存在一個問題,服務端與redis的連接次數會比較多,如果一個用戶下單商品種類較多,那么仍然會比較慢,但這確實不失為一個好的方案。”

“既然這個方案仍然有可能有問題,那么還有沒有其它的方案。”

“數據庫本身是有鎖的,可以實現鎖同步的問題,那么有沒有辦法使用到數據庫的鎖來解決這個問題?”

“對呀,我們可以寫SQL語句去循環扣減庫存,最后判斷數據庫影響行數與商品種類是否匹配?如果不匹配,則是扣減失敗,進行還原,如果匹配,則扣減成功!”

“經過測試,我們用的MySQL不支持這種方案,里面需要用到if判斷,而if判斷必須要在存儲過程中才能使用。”

“那我們可以使用存儲過程來做。代碼如下”

DELIMITER $$
USE anke_skucenter$$
CREATE PROCEDURE minusSkuNum()
BEGIN
SET AUTOCOMMIT=0;
START TRANSACTION;
UPDATE SKU_Info SET skuNum=skuNum-100 WHERE id='0031394c-8058-49f5-9ba9-f971480ac2f2' AND skuNum>=100;
 IF(SELECT ROW_COUNT()<=0)THEN
	ROLLBACK;
	END IF;
UPDATE `SKU_Info`  SET skuNum=skuNum-1000 WHERE id='00293cb7-d8cf-4470-a66d-bb45ca2b130000293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000;
IF(SELECT ROW_COUNT()<=0)THEN
	ROLLBACK;
END IF;
COMMIT;
SET AUTOCOMMIT=1;
END$$

 “這個是初步的存儲過程,仍然需要將update語句變更為循環,改變傳入參數為商品id和數量,有誰會寫?”

“額,目前大家都不會寫,並且這個循環看上去也挺復雜的。”

“那么我們能不能在mybatis中獲取多條更新語句的影響行數?”

“不能,沒有任何框架支持,並且mysql本身就不支持,要不然也不會需要存儲過程了。”

“既然多條SQL不行,能不能放到一條SQL中去做更新呢?”

“先baidu下”

“哈哈,找到了,我們查詢的時候有時候回用到case when,那么我們更新的時候是否可以使用這個呢?嘗試代碼如下:”

update SKU_Info set skuNum=skuNum-(case when id='0031394c-8058-49f5-9ba9-f971480ac2f2'  then 100 
when id='00293cb7-d8cf-4470-a66d-bb45ca2b1300' then 1000 end)
where (id='0031394c-8058-49f5-9ba9-f971480ac2f2' AND skuNum>=100) 
or (id='00293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000);

 “經過測試,該段代碼執行正常,並且能夠正常返回需要的影響行數。”

“這個循環的SQL編寫在mybatis中不難,那么判斷最后扣減結果與商品種類不同時,如何進行補償呢?”

“這個可以使用@Transactional,我們在方法上加此注解,在方法內部判斷,如果不同,我們就拋出一個自定義異常,這樣就會自動進行回滾了。”

“測試一下”

“經過幾輪測試,確實可行,就這樣做。”

“具體實施為:先生成訂單,然后進行扣減,如果捕獲到扣減失敗的自定義異常,則對生成的訂單執行刪除標記。但存在一個問題,就是標記訂單為刪除狀態失敗的情況,這個訂單仍然存在,也是超賣了。”

“可以調整下,改為先進行扣減,扣減成功再生成訂單,這樣可以避免此問題。”

“嗯,此方法可以解決超賣問題,可能會存在商品扣減成功,但訂單未生成的情況。”

“這種問題會存在,但比超賣要好很多。”

“嗯嗯”

“嗯嗯”


免責聲明!

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



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