面試必備的數據庫悲觀鎖與樂觀鎖

前言

在上一個章節5分鐘帶你讀懂事務隔離性與隔離級別 的最後,其實咱們已經提到了鎖的概念。本章節接下來將主要介紹如下數據庫悲觀鎖與樂觀鎖的相關知識。若有錯誤還請你們及時指出~mysql

本文已同步至 GitHub/Gitee/公衆號,感興趣的同窗幫忙點波關注~git

問題:github

  • 爲何須要鎖?
  • 什麼是悲觀鎖?
  • 什麼是樂觀鎖?
  • 悲觀鎖與樂觀鎖區別與聯繫?
  • 悲觀鎖與樂觀鎖的使用場景?

爲何須要鎖?

在併發環境下,若是多個客戶端訪問同一條數據,此時就會產生數據不一致的問題,如何解決,經過加鎖的機制,常見的有兩種鎖,樂觀鎖和悲觀鎖,能夠在必定程度上解決併發訪問。sql

1. 悲觀鎖(Pessimistic Lock)

1.1 定義

百度百科:數據庫

悲觀鎖,正如其名,具備強烈的獨佔和排他特性。它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。編程

其餘知識點bash

悲觀鎖主要是共享鎖排他鎖 共享鎖又稱爲讀鎖,簡稱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;(等待中...)

流程說明

  1. 用戶A start transaction開啓一個事物。前一步咱們關閉了mysql的autocommit,因此須要手動控制事務的提交。
  2. 在得到小米手機信息(productId = 1 )時,進行數據加鎖操做(for update)。與普通查詢方式不一樣,咱們使用了select…for update的方式,這樣就經過數據庫實現了悲觀鎖。在這個update事務提交以前其餘外界是不能修改這條數據的,可是這種處理方式效率比較低,通常不推薦使用。
  3. 用戶B start transaction開啓一個事物。
  4. 用戶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;

流程說明

  1. 事務A開啓事務。
  2. 事務A查詢當前小米手機數量爲100。
  3. 事務A購買小米手機,小米手機數量更新爲99。(此時並未提交事務)。
  4. 事務B開啓事務。
  5. 事務B查詢當前小米手機數量爲100。
  6. 事務B購買小米手機,小米手機數量更新爲99。注意:此時處於阻塞狀態。
  7. 事務A提交事務。
  8. 此時第六步執行完畢,但並未成功(受影響的行: 0)。
  9. 事務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編程 獲取最新原創技術文章和相關免費學習資料,隨時隨地學習技術知識!

參考文章:

chenzhou123520.iteye.com/blog/186095…

chenzhou123520.iteye.com/blog/186340…

微信公衆號

推薦閱讀

帶你瞭解數據庫中JOIN的用法

帶你瞭解數據庫中事務的ACID特性

5分鐘帶你讀懂事務隔離性與隔離級別

相關文章
相關標籤/搜索