實戰3:如何使用樂觀鎖
1. 前言
在鎖一節中,我們從粒度
和管理
兩個角度來闡述了鎖。如果你還不熟悉鎖,請先閱讀該小節,再來進行本小節的學習。
本小節我們將繼續深挖鎖
,以開發者和實戰的角度來談鎖。
2. 為什麼需要鎖
2.1 什麼是資料競爭
在本節的開頭,我們來談一談為什麼開發程式需要使用鎖?如果你有一點併發程式設計的基礎,又或者對多執行緒有一點熟悉,那麼你肯定知道答案,那就是資料競爭
。
2.2 資料競爭例項
我們舉一個生活的例子。在中學的時候,教室的前面會放上一塊小黑板,小黑板上會記載某一天上交作業或誰誰誰打掃衛生之類的。那塊小黑板是所有同學都可使用的,只要你有事情要公佈,就可以寫在上面。
那麼問題來了,假設同學A
用小黑板寫上明天要上交的作業,此時同學B
A
和B
來說,他們之間存在競爭關係,而小黑板就是競爭點。
直觀上來說,如果A
比B
早到,那麼A
就可以佔有小黑板,換言之A
給小黑板加上了一把鎖,B
不能使用小黑板。A
寫完了,把小黑板再次放到了教室前,相當於釋放了鎖,此時B
才可書寫小黑板,即B
拿到了鎖。
因此,鎖的出現是為了解決併發
中存在的資料競爭
問題。
3. 樂觀鎖和悲觀鎖
樂觀
與悲觀
是兩種不同的態度,從名字上看,二者就是以開發者的態度作為邊界來分類的。
樂觀鎖認為,同一資料在併發條件下,發生衝突是小概率事件,因此我們不加鎖,而是加上版本號判斷修改是否成功。
悲觀鎖認為,同一資料在併發條件下,衝突是大概率事件,因此我們必須先加鎖,不允許別人修改。
悲觀鎖和樂觀鎖其實是一種思想,主要取決於開發者對待它的態度。在鎖這一小節中,裡面談到的所有鎖巨集觀上(可能實現的思想是樂觀鎖)來說都是悲觀鎖,因此一旦加鎖,都會鎖定資料,直到解鎖才會釋放。
3.1 樂觀鎖實施方案
樂觀鎖不全依賴於資料庫,一般情況下我們都是在程式碼層面上來完成它的,主流的設計思路是這樣的:
我們在資料表中新增一個欄位version
,version 代表版本號,欄位型別為整型。當我們獲取資料時,假設得到它的version
欄位為n
,執行完其它操作對該資料進行更新時,會執行UPDATE ... SET version=n+1 WHERE version=n
。
如果在更新時,資料已經被別人更新過了,那麼該資料的version
n
了,那麼此時修改就會失敗,反之修改就會成功。
可以看到,樂觀鎖就像它的名稱一樣樂觀,適合資料讀多寫少
的場景,因為實際上並沒鎖住資料,所以效能十分可觀;而悲觀鎖則與之相反,適合寫多讀少的場景,盲目的排他性一定程度上會大幅影響效能。
4. 實踐
4.1 樂觀鎖資料表
樂觀鎖的使用十分廣泛,我們也推薦你在實際的開發中使用樂觀鎖,接下來,我們以一個例子來詳細的說明一下樂觀鎖。
我們新建一個測試資料表 imooc_order :
DROP TABLE IF EXISTS imooc_order;
CREATE TABLE imooc_order
(
id int PRIMARY KEY,
price decimal(10,2),
-- version 欄位作為樂觀鎖版本控制位
version int NOT NULL DEFAULT 0
);
INSERT INTO imooc_order(id,price,version)
VALUES (1,23.2,1);
注意: 我們已經在表中添加了 version 欄位
4.2 樂觀鎖例項
imooc_order
表存放了訂單資訊(簡略資訊),而訂單的價格並非一成不變的,它可能會同時被多個人改變。
那麼如何能夠安全地修改它的價格,且不會跟別人衝突了。
現在預設有兩個人,甲
現在拿到了id
為1
的訂單,想要修改它的價格:
SELECT * FROM imooc_order WHERE id = 1;
在甲
拿到的同時,乙
也同樣拿到了訂單資料,且訂單此時的價格為23.2
,版本號為1
。
甲
決定修改訂單的價格為33.3
,於是他執行了如下語句:
UPDATE imooc_order SET version = 1 + 1, price=33.3 WHERE id = 1 AND version = 1;
甲
執行成功了,而此時乙
也需要修改價格,但是他並不知道價格已經修改:
UPDATE imooc_order SET version = 1 + 1, price=22.1 WHERE id = 1 AND version = 1;
很明顯,乙
修改失敗了,因為在他修改價格之前,甲
以微弱的速度優勢已經修改了價格,且修改了 version
欄位,此時 version
等於2
。
而乙
提交 SQL 語句時,Where 中明確的寫到 version 等於 1。即使乙修改失敗,但是資料仍然是正確的,乙
完全可以在失敗的情況下重複獲取一次資料再修改。
如下圖所示:
4.3 樂觀鎖總結
可以看到,樂觀鎖雖然有缺陷,它會使更新失敗,因此必須重複獲取資料然後重試,但是它保證了資料的正確性和完整性。在讀多寫少的場景下,樂觀鎖不會出現太多的重試,當然如果出現了很多重試,證明場景已經可能不是讀多寫少了,可以嘗試換方案了。
樂觀鎖的實現也頗為簡單,不需要任何第三方依賴,你完全可以自己直接實現,不過仍然有一些第三方框架提供了開箱即用的樂觀鎖,你可以根據自己的使用語言和生態去查詢相應的樂觀鎖框架。
5. 小結
- 樂觀鎖和悲觀鎖同等重要,樂觀鎖是很多高併發場景下的基石。
- 大多數時候,程式使用的都是悲觀鎖,如常見的
自旋鎖
。 - 樂觀鎖與悲觀鎖都是一種思路,熟悉並掌握該思路,任何面試都攔不到你。