数据库 / 悲观锁和乐观锁
一、什么是悲觀鎖,什么是樂觀鎖
鎖(Lock)
在介紹悲觀鎖和樂觀鎖之前,讓我們看一下鎖。鎖,在我們生活中隨處可見,我們的門上有鎖,我們存錢的保險柜上有鎖,是用來保護我們財產安全的。程序中也有鎖,當多個線程修改共享變量時,我們可以給修改操作上鎖(syncronized)。當多個用戶修改表中同一數據時,我們可以給該行數據上鎖(行鎖)。因此,鎖其實是在并發下控制多個操作的順序執行,以此來保證數據安全的變動。 并且,鎖是一種保證數據安全的機制和手段,而并不是特定于某項技術的。悲觀鎖和樂觀鎖亦是如此。本篇介紹的悲觀鎖和樂觀鎖是基于數據庫層面的。
悲觀鎖(Pessimistic Concurrency Control)
第一眼看到它,相信每個人都會想到這是一個悲觀的鎖。沒錯,它就是一個悲觀的鎖。那這個悲觀體現在什么地方呢?悲觀是我們人類一種消極的情緒,對應到鎖的悲觀情緒,悲觀鎖認為被它保護的數據是極其不安全的,每時每刻都有可能變動,一個事務拿到悲觀鎖后(可以理解為一個用戶),其他任何事務都不能對該數據進行修改,只能等待鎖被釋放才可以執行。
數據庫中的行鎖,表鎖,讀鎖,寫鎖,以及 syncronized 實現的鎖均為悲觀鎖。
這里再介紹一下什么是數據庫的表鎖和行鎖,以免有的同學對后面悲觀鎖的實現看不明白。
我們經常使用的數據庫是 mysql,mysql 中最常用的引擎是 Innodb,Innodb 默認使用的是行鎖。而行鎖是基于索引的,因此要想加上行鎖,在加鎖時必須命中索引,否則將使用表鎖。
樂觀鎖(Optimistic Concurrency Control)
與悲觀相對應,樂觀是我們人類一種積極的情緒。樂觀鎖的“樂觀情緒”體現在,它認為數據的變動不會太頻繁。因此,它允許多個事務同時對數據進行變動。 但是,樂觀不代表不負責,那么怎么去負責多個事務順序對數據進行修改呢?樂觀鎖通常是通過在表中增加一個版本(version)或時間戳(timestamp)來實現,其中,版本最為常用。
事務在從數據庫中取數據時,會將該數據的版本也取出來(v1),當事務對數據變動完畢想要將其更新到表中時,會將之前取出的版本 v1 與數據中最新的版本 v2 相對比,如果 v1 = v2 ,那么說明在數據變動期間,沒有其他事務對數據進行修改,此時,就允許事務對表中的數據進行修改,并且修改時 version 會加1,以此來表明數據已被變動。如果,v1 不等于 v2,那么說明數據變動期間,數據被其他事務改動了,此時不允許數據更新到表中,一般的處理辦法是通知用戶讓其重新操作。不同于悲觀鎖,樂觀鎖是人為控制的。
二、怎么實現悲觀鎖,怎么實現樂觀鎖
經過上面的學習,我們知道悲觀鎖和樂觀鎖是用來控制并發下數據的順序變動問題的。那么我們就模擬一個需要加鎖的場景,來看不加鎖會出什么問題,并且怎么利用悲觀鎖和樂觀鎖去解決。
從表中可以看到豬肉脯目前的數量只有 1 個了。在不加鎖的情況下,如果 A,B 同時下單,就會報錯。
悲觀鎖解決
利用悲觀鎖的解決思路是,A 下單前先給豬肉脯這行數據(id=1)加上悲觀鎖(行鎖)。此時這行數據只能 A 來操作,也就是只有 A 能買。B 想買就必須一直等待。當 A 買好后,B 再想去買的時候會發現數量已經為 0,那么 B 看到后就會放棄購買。
那么如何給豬肉脯也就是 id=1 這條數據加上悲觀鎖鎖呢?我們可以通過以下語句給 id=1 的這行數據加上悲觀鎖
select num from goods where id = 1 for update;下面是悲觀鎖的加鎖圖解
我們通過開啟 mysql 的兩個會話,也就是兩個命令行來演示。
1、事務 A 執行命令給 id = 1 的數據上悲觀鎖準備更新數據。
這里之所以要以 begin 開始,是因為 mysql 是自提交的,所以要以 begin 開啟事務,否則所有修改將被 mysql 自動提交。
2、事務 B 也去給 id = 1 的數據上悲觀鎖準備更新數據
我們可以看到此時事務 B 再一直等待 A 釋放鎖。如果 A 長期不釋放鎖,那么最終事務B將會報錯,這有興趣的可以去嘗試一下。
3、接著我們讓事務 A 執行命令去修改數據,讓豬肉脯的數量減一,然后查看修改后的數據,最后commit,結束事務。
我們可以看到,此時最后一個豬肉脯被 A 買走,只剩 0 個了。
4、當事務 A 執行完第 3 步后,我們看事務 B 中出現了什么
我們看到由于事務 A 釋放了鎖,事務 B 就結束了等待,拿到了鎖,但是數據此時變成了 0,那么 B 看到后就知道被買走了,就會放棄購買。
通過悲觀鎖,我們解決了豬肉脯購買的問題。
樂觀鎖解決
下面,我們利用樂觀鎖來解決該問題。上面樂觀鎖的介紹中,我們提到了,樂觀鎖是通過版本號 version 來實現的。 所以,我們需要給 goods 表加上 version 字段,表變動后的結構如下
id、name、num、version
1???? 豬肉脯?? 1???????????? 0
2???? 牛肉干?? 1???????????? 0
具體的解決思路是,A 和 B 同時將豬肉脯(id = 1 下面都說是 id = 1)的數據查出來,然后 A 先買,A 將 id = 1 和 version = 0 作為條件進行數據更新,即將數量減一,并且將版本號加一。此時版本號變為1。A 此時就完成了商品的購買。最后 B 開始買,B 也將 id = 1 和 version = 0 作為條件進行數據更新,但是更新完后,發現更新的數據行數為 0,此時就說明已經有人改動過數據,此時就應該提示用戶重新查看最新數據購買。
下面是樂觀鎖的加鎖圖解
我們還是通過開啟mysql的兩個會話,也就是兩個命令行來演示。
1、事務 A 執行查詢命令,事務 B 執行查詢命令,因為兩者查詢的結果相同,所以下面我只列出一個截圖。
此時 A 和 B 均獲取到相同的數據。
2、事務 A 進行購買更新數據,然后再查詢更新后的數據。
我們可以看到事務 A 成功更新了數據和版本號。
事務 B 再進行購買更新數據,然后我們看影響行數和更新后的數據。
可以看到最終修改行數為 0,數據沒有改變。此時就需要我們告知用戶重新處理。
三、樂觀鎖和悲觀鎖的優缺點
下面我們介紹下樂觀鎖和悲觀鎖的優缺點以便我們分析他們的應用場景,這里我只分析最重要的優缺點,也是我們要記住的。
1、悲觀鎖
- 優點:悲觀鎖利用數據庫中的鎖機制來實現數據變化的順序執行,這是最有效的辦法。
- 缺點:一個事務用悲觀鎖對數據加鎖之后,其他事務將不能對加鎖的數據進行除了查詢以外的所有操作,如果該事務執行時間很長,那么其他事務將一直等待,那勢必影響我們系統的吞吐量。
2、樂觀鎖
- 優點:樂觀鎖不在數據庫上加鎖,任何事務都可以對數據進行操作,在更新時才進行校驗,這樣就避免了悲觀鎖造成的吞吐量下降的劣勢。
- 缺點:樂觀鎖因為時通過我們人為實現的,它僅僅適用于我們自己業務中,如果有外來事務插入,那么就可能發生錯誤。
四、樂觀鎖和悲觀鎖的應用場景
1、悲觀鎖
因為悲觀鎖會影響系統吞吐的性能,所以適合應用在寫為居多的場景下。
2、樂觀鎖
因為樂觀鎖就是為了避免悲觀鎖的弊端出現的,所以適合應用在讀為居多的場景下。
轉載于:https://zhuanlan.zhihu.com/p/63714157
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的数据库 / 悲观锁和乐观锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 杭州亲历重要时间点记录
- 下一篇: linux cmake编译源码,linu