前言
在上一個章節5分鍾帶你讀懂事務隔離性與隔離級別 的最后,其實我們已經提到了鎖的概念。本章節接下來將主要介紹以下數據庫悲觀鎖與樂觀鎖
的相關知識。如有錯誤還請大家及時指出~
問題:
- 為什么需要鎖?
- 什么是悲觀鎖?
- 什么是樂觀鎖?
- 悲觀鎖與樂觀鎖區別與聯系?
- 悲觀鎖與樂觀鎖的使用場景?
為什么需要鎖?
在並發環境下,如果多個客戶端訪問同一條數據,此時就會產生數據不一致的問題,如何解決,通過加鎖的機制,常見的有兩種鎖,樂觀鎖和悲觀鎖,可以在一定程度上解決並發訪問。
1. 悲觀鎖(Pessimistic Lock)
1.1 定義
百度百科:
悲觀鎖,正如其名,具有強烈的獨占和排他特性。它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處於鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
其他知識點
悲觀鎖主要是共享鎖
或排他鎖
共享鎖
又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。
排他鎖
又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他所並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就行讀取和修改。
1.2 案例分析
使用場景舉例:以MySQL InnoDB為例
作為演示,我們繼續使用之前的數據庫表:product表
productId | productName | productPrice | productCount |
---|---|---|---|
1 | 小米 | 1999 | 100 |
2 | 魅族 | 1999 | 100 |
首先我們需要set autocommit=0
,即不允許自動提交
有看過上一篇文章5分鍾帶你讀懂事務隔離性與隔離級別 的同學,可以看到最后我們使用事務隔離級別時,所引申出來的根本問題就是可以通過鎖機制解決。
問題
在並發情況下回導致數據一致性的問題:
如果有A、B兩個用戶需要搶productId =1的小米手機,A、B用戶都查詢小米手機數量是100,A購買后修改商品的數量為99,B購買后修改數量為99。
用法
每次獲取小米手機時,對該商品加排他鎖。也就是在用戶A獲取獲取 id=1 的小米手機信息時對該行記錄加鎖,期間其他用戶阻塞等待訪問該記錄。代碼如下:
start transaction;
select p.productCount from product p where p.productId = 1 for update;
update product p set p.productCount=p.productCount-1 where p.productId=1 ;
commit;
操作
下面同時打開兩個窗口模擬2個用戶並發訪問數據庫
時間軸 | 事務A | 事務B |
---|---|---|
T1 | start transaction; | |
T2 | select p.productCount from product p where p.productId = 1 for update; | |
T3 | start transaction; | |
T4 | select p.productCount from product p where p.productId = 1 for update;(等待中...) |
流程說明
- 用戶A start transaction開啟一個事物。前一步我們關閉了mysql的autocommit,所以需要手動控制事務的提交。
- 在獲得小米手機信息(productId = 1 )時,進行數據加鎖操作(for update)。與普通查詢方式不同,我們使用了
select…for update
的方式,這樣就通過數據庫實現了悲觀鎖。在這個update事務提交之前其他外界是不能修改這條數據的,但是這種處理方式效率比較低,一般不推薦使用。 - 用戶B start transaction開啟一個事物。
- 用戶B 也進行查詢操作,此時處於等待中(阻塞狀態)。ps:需要等待用戶A事務提交后,才會執行。
注意:在事務中,只有select…for update(排他鎖) 或lock in share mode(共享鎖) 操作同一個數據時才會等待其它事務結束后才執行,一般select... 則不受此影響。例如在 T3中執行select p.productCount from product p where p.productId = 1;則能正常查詢出數據,不會受第一個事務的影響。
2. 樂觀鎖(Optimistic Lock)
2.1 定義
百度百科:
樂觀鎖機制采取了更加寬松的加鎖機制。樂觀鎖是相對悲觀鎖而言,也是為了避免數據庫幻讀、業務處理時間過長等原因引起數據處理錯誤的一種機制,但樂觀鎖不會刻意使用數據庫本身的鎖機制,而是依據數據本身來保證數據的正確性。
其他知識點
實現樂觀鎖一般來說有以下2種方式:
-
使用版本號
使用數據版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式。何謂數據版本?即為數據增加一個版本標識,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現。當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加一。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對,如果數據庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期數據。 -
使用時間戳
樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個字段,名稱無所謂,字段類型使用時間戳(timestamp), 和上面的version類似,也是在更新提交的時候檢查當前數據庫中數據的時間戳和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本沖突。
2.2 案例分析
使用場景舉例:以MySQL InnoDB為例
作為演示,我們繼續使用之前的數據庫表:product表
productId | productName | productPrice | productCount | version |
---|---|---|---|---|
1 | 小米 | 1999 | 100 | 1 |
2 | 魅族 | 1999 | 100 | 2 |
我們以版本號
實現的方式進行說明。
操作
查詢當前事務隔離級別:
SELECT @@tx_isolation;
結果:
REPEATABLE-READ
下面同時打開兩個窗口模擬2個用戶並發訪問數據庫
第一種測試
時間軸 | 用戶A | 用戶B |
---|---|---|
T1 | start transaction; | |
T2 | select * from product p where p.productId = 1;(productCount=100) | |
T3 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影響的行: 1) | |
T4 | start transaction; | |
T5 | select * from product p where p.productId = 1;(productCount=100) | |
T6 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(等待中...) | |
T7 | commit; | |
T8 | T6執行(受影響的行: 0) | |
T9 | commit; |
流程說明
- 事務A開啟事務。
- 事務A查詢當前小米手機數量為100。
- 事務A購買小米手機,小米手機數量更新為99。(此時並未提交事務)。
- 事務B開啟事務。
- 事務B查詢當前小米手機數量為100。
- 事務B購買小米手機,小米手機數量更新為99。注意:此時處於阻塞狀態。
- 事務A提交事務。
- 此時第六步執行完畢,但並未成功(受影響的行: 0)。
- 事務B提交事務。
第二種測試
時間軸 | 用戶A | 用戶B |
---|---|---|
T1 | select * from product p where p.productId = 1;(productCount=100) | |
T2 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影響的行: 1) | |
T3 | select * from product p where p.productId = 1;(productCount=100) | |
T4 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影響的行: 0) |
樂觀鎖小結
- 用戶B修改數據的時候,受影響行數為0,對業務來說,及更新失敗。這時候我們只需要告訴用戶購買失敗,重新查詢一遍即可。
- 對比第一種和第二種測試,我們會發現第一種測試,將update語句放入事務中會出現阻塞的情況,而第二種測試不會出現阻塞情況。這是為什么呢?update其實在不在事務中都無所謂,在內部是這樣的:update是單線程的,及如果一個線程對一條數據進行update操作,會獲得鎖,其他線程如果要對同一條數據操作會阻塞,直到這個線程update成功后釋放鎖。
樂觀鎖不需要數據庫底層的支持!
3. 適用場景
悲觀鎖
比較適合寫入操作比較頻繁的場景,如果出現大量的讀取操作,每次讀取的時候都會進行加鎖,這樣會增加大量的鎖的開銷,降低了系統的吞吐量。
樂觀鎖
比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,數據發生沖突的可能性就會增大,為了保證數據的一致性,應用層需要不斷的重新獲取數據,這樣會增加大量的查詢操作,降低了系統的吞吐量。
文末
本章節主要簡單介紹了數據庫中樂觀鎖與悲觀鎖
的相關知識,后續我們將會繼續介紹數據庫中的其他鎖以及相關知識。例如行鎖、表鎖、死鎖、
歡迎關注個人微信公眾號:Coder編程
獲取最新原創技術文章和免費學習資料,更有大量精品思維導圖、面試資料、PMP備考資料等你來領,方便你隨時隨地學習技術知識!
新建了一個qq群:315211365,歡迎大家進群交流一起學習。謝謝了!也可以介紹給身邊有需要的朋友。
文章收錄至
Github: https://github.com/CoderMerlin/coder-programming
Gitee: https://gitee.com/573059382/coder-programming
歡迎關注並star~
參考文章:
https://chenzhou123520.iteye.com/blog/1860954
https://chenzhou123520.iteye.com/blog/1863407