使用MySQL樂觀鎖解決超賣問題

在秒殺系統設計中,超賣是一個經典、常見的問題,任何商品都會有數量上限,如何避免成功下訂單買到商品的人數不超過商品數量的上限,這是每個搶購活動都要面臨的難點。

1 超賣問題描述

在多個用戶同時發起對同一個商品的下單請求時,先查詢商品庫存,再修改商品庫存,會出現資源競爭問題,導致庫存的最終結果出現異常。

問題:當商品A一共有庫存15件,用戶甲先下單10件,用戶乙下單8件,這時候庫存只能滿足一個人下單成功,如果兩個人同時提交,就出現了超賣的問題。

可以採用多種方式解決超賣問題。使用synchronized可以保證數據一致性,但是效率低,並且分佈式環境下無用;使用數據庫鎖表會造成數據庫性能低下。單體條件下,採用樂觀鎖是比較合適的方式,集羣可以考慮分佈式鎖

2 樂觀鎖

2.1 樂觀鎖介紹

悲觀鎖,認爲數據很容易被其他線程修改,爲保證數據正確性,每次獲取並修改數據時,對數據加鎖。例如Java中的synchronized和Lock相關類。

而樂觀鎖,認爲自己在操作時不會有其他線程干擾,所以不對被操作對象加鎖。在更新時會判斷修改期間是否有其他線程修改過。如果沒被修改過,則表示只有當前線程在操作,正常修改數據。如果數據被其他線程修改過,則會停止剛纔的更新,選擇執行策略,例如拋棄、報錯、重試等。

樂觀鎖一般使用CAS算法實現。例如Java中的原子類、併發容器。

2.2 沒有鎖的更新操作

樂觀鎖,不是數據庫功能,是一種數據庫實踐。假設進行以下操作:從表中獲取某行數據,計算數據,更新數據該行數據。

CREATE TABLE theTable(
    iD int NOT NULL,
    val1 int NOT NULL,
    val2 int NOT NULL
)
INSERT INTO theTable (iD, val1, val2) VALUES (1, 2 ,3);

沒有鎖的處理

-- 查詢數據
SELECT iD, val1, val2
FROM theTable
WHERE iD = @theId;
-- 計算新值
-- 更新數據
UPDATE
	theTable
SET
	val1 = @newVal1,
	val2 = @newVal2
WHERE
	iD = @theId;
-- 繼續執行

2.3 樂觀鎖的實現方式1--條件控制

--查詢數據
SELECT iD, val1, val2
FROM theTable
WHERE iD = @theId;
--計算新值
--更新數據
UPDATE
	theTable
SET
	val1 = @newVal1,
	val2 = @newVal2
WHERE
	iD = @theId
	AND val1 = @oldVal1
	AND val2 = @oldVal2;
--判斷影響行數 
-- {if AffectedRows == 1 } 
-- 		{繼續執行}
-- {else} 
-- 		{數據過期}
-- {endif}

上面操作的關鍵在於,UPDATE指令的結構與後續受影響的行數檢查,從而判斷是否有人修改數據。上面所有操作沒有使用事務,這也表明樂觀鎖的關鍵不在於事務本身。

2.4 擴展:事務的使用

--查詢數據
SELECT iD, val1, val2
FROM theTable
WHERE iD = @theId;
--計算新值
--開始事務,更新數據
UPDATE
	theTable
SET
	val1 = @newVal1,
	val2 = @newVal2
WHERE
	iD = @theId
	AND val1 = @oldVal1
	AND val2 = @oldVal2;
--判斷影響行數 
-- {if AffectedRows == 1 }
--	   COMMIT TRANSACTION; // 提交事務
--     {繼續執行}
-- {else}
--     ROLLBACK TRANSACTION; // 回滾事務
--     {數據過期}
-- {endif}

使用了事務,便可以回滾修改。通過事務,我們可以確定每次回滾的操作量是多少,在何處放置事務邊界以及在何處檢查衝突。

對於其他進程在當前事務提交之前,會發生什麼,取決於數據庫當前的隔離級別。以SQL Server爲例,其隔離級別是READ_COMMITTED,更新的行被鎖定,直到COMMIT爲止,因此“其他進程”無法對該行執行任何操作(保持等待狀態),而SELECT(實際上只能執行READ_COMMITTED) 。

2.5 樂觀鎖的實現方式2--版本號

使用版本號,也是樂觀鎖常用實現方式。通過在表中增加一個version字段:讀取數據時,將version字段值一併讀出,數據更新一次,則version值加1。當我們提交更新時,判斷表中最新的version值與之前讀出的version值是否一致,如果一致,則更新,否則視爲過期數據。

--查詢數據
SELECT iD,val1,val2,VERSION
FROM theTable
WHERE iD = @theId;
--計算新值
UPDATE
	theTable
SET
	val1 = @newVal1,
	val2 = @newVal2,
	VERSION = VERSION + 1
WHERE
	iD = @theId
	AND VERSION = @oldversion;
--判斷影響行數 
-- {if AffectedRows == 1 } 
-- 		{繼續執行}
-- {else} 
-- 		{數據過期}
-- {endif}

參考資料

https://stackoverflow.com/questions/17431338/optimistic-locking-in-mysql

本文由博客一文多發平臺 OpenWrite 發佈!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章