InnoDB 事務加鎖分析

本文首發於 vivo互聯網技術 微信公衆號 
連接: https://mp.weixin.qq.com/s/S7MhlsZveBHRSQhq5aTIJA
做者:何志創

通常你們對數據庫事務的瞭解可能停留在事務的ACID特性以及事務4種不一樣的隔離級別層面上,而對於事務 4 種不一樣隔離級別如何實現瞭解相對較少。數據庫

本文以 MySQL 數據庫 InnoDB 引擎爲例,爲你們分析 InnoDB數據庫引擎對默認的隔離級別可重複讀(RR)的具體實現。微信

整文知識點介紹:事務4種隔離級別、不一樣隔離級別解決的問題、MVCC、鎖的類型、加鎖案例分析;閱讀完整文相信你們對事務隔離級別的具體實現有了必定的認識。併發

1、事務的隔離級別

一、4 種隔離級別

(1)未提交讀(Read uncommitted):一個事務讀取到其餘事務未提交的數據,是級別最低的隔離機制;spa

(2)提交讀(Read committed):一個事務讀取到其餘事務提交後的數據;blog

(3)可重複讀(Repeatable read):一個事務對同一份數據讀取到的相同,不在意其餘事務對數據的修改;索引

(4)序列化(Serializable) :事務串行化執行,隔離級別最高,犧牲了系統的併發性。事務

二、不一樣隔離級別解決的問題

若不考慮事務的隔離級別,則事務的併發會形成如下問題:rem

(1)髒讀:事務A讀取了事務B更新的數據,而後B回滾操做,那麼A讀取到的數據是髒數據。get

(2)不可重複讀:事務 A 屢次讀取同一數據,事務 B 在事務A屢次讀取的過程當中,對數據做了更新並提交,致使事務A屢次讀取同一數據時,結果 不一致。it

(3)幻讀:同一事務中對同一範圍的數據進行讀取,結果卻多出了數據或者少了數據,這就叫幻讀。(如同一事務對id<10的範圍進行2次查詢,第一次出現id=八、9的兩條數據,第二次出現id=七、八、9的3條數據)。

不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表。

不一樣的隔離級別針對上述3個問題的解決能力,以下表:

2、MVCC

上文提到 InnoDB 默認的隔離級別是可重複讀(RR),InnoDB是經過MVCC(多版本併發控制)來實現可重複讀的,下面爲你們介紹MVCC。

一、概念

在InnoDB中,給每行增長兩個隱藏字段來實現MVCC,一個用來記錄數據行的建立時間,另外一個用來記錄行的過時時間(刪除時間)。在實際操做中,存儲的並非時間,而是事務的版本號,每開啓一個新事務,事務的版本號就會遞增。

因而乎,默認的隔離級別(REPEATABLE READ)下,增刪查改變成了這樣:

(1)SELECT

  • 讀取建立版本小於或等於當前事務版本號,而且刪除版本爲空或大於當前事務版本號的記錄。這樣能夠保證在讀取以前記錄是存在的。

(2)INSERT

  • 將當前事務的版本號保存至行的建立版本號。

(3)UPDATE

  • 新插入一行,並以當前事務的版本號做爲新行的建立版本號,同時將原記錄行的刪除版本號設置爲當前事務版本號。

(4)DELETE

  • 將當前事務的版本號保存至行的刪除版本號。

二、快照讀和當前讀

(1)快照讀:讀取的是快照版本,也就是歷史版本;

(2)當前讀:讀取的是最新版本。

普通的SELECT就是快照讀,而UPDATE、DELETE、INSERT、SELECT ...  LOCK IN SHARE MODE、SELECT ... FOR UPDATE是當前讀。

(3)結論:若是隔離級別是REPEATABLE READ,那麼在同一個事務中的全部普通select讀讀到的都是事務第一個讀到的快照,如此實現了可重複讀;而對於當前讀(UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE),InnoDB 經過加鎖來實現可重複讀,且InnoDB 加鎖同時解決了幻讀問題。

3、鎖的類型

InnoDB 引入如下三種鎖類型:

  • Record Locks(記錄鎖):在索引記錄上加鎖,即行鎖,鎖住當前行。
  • Gap Locks(間隙鎖):在索引記錄之間加鎖,或者在第一個索引記錄以前加鎖,或者在最後一個索引記錄以後加鎖。
  • Next-Key Locks:在索引記錄上加鎖,而且在索引記錄以前的間隙加鎖。它至關因而Record Locks與Gap Locks的一個結合。

假設一個索引包含如下幾個值:10,11,13,20。那麼這個索引的next-key鎖將會覆蓋如下區間:(-oo, 10]、(10, 11]、(11, 13]、(13, 20]、(20, +oo)。

MySQL InnoDB 經過間隙鎖解決了幻讀問題。如下經過實際的案例分析來介紹InnoDB 是若是解決幻讀問題的。

4、案例分析

在對SQL進行加鎖分析前,須要明確表的結構和索引類型。在不知道索引的狀況下直接給出一條SQL來分析若是加鎖是沒有任何意義的。

如下以用戶表(t_user)爲例(id爲主鍵,name爲惟一索引,age爲通常索引,address無索引)分析不一樣索引條件的加鎖表現。

一、主鍵索引

例:delete from t_user where id=120;
條件爲主鍵,此時鎖住聚簇索引中對應的行記錄:即Record Locks鎖住id=120的行記錄。

此種狀況下,其餘事務除了不能刪除、更新此條記錄外,其餘插入其餘行、更新其餘行都行。

SQL驗證:

二、惟一索引

例:delete from t_user where name='n20';
條件爲惟一索引,鎖住索引記錄,同時鎖住聚簇索引中的對應行記錄:

SQL驗證:

三、通常索引

例:delete from t_user where age=20;
與主鍵和惟一索引不一樣的是,通常索引的記錄是容許重複的;換句話說,若是咱們單純地給索引加記錄鎖時,其餘事務依然能夠插入,也就有可能出現幻讀問題了。

因此除了給對應索引記錄加上記錄鎖以外,還要給Gap加上鎖。

從上面知識點咱們能夠預估這個操做一共須要的鎖:

  • age索引記錄鎖(Record Lock) :

    20_120, 20_130(如下均用age_id這種形式表示索引值)

  • age索引間隙鎖(Gap X-Lock):

    (10, 20)、(20, 20)、(20, 40)

  • 聚簇索引上的記錄鎖(Record X-Lock):

    id=120/130對應的行記錄

SQL驗證:

根據實際狀況,3-6均符合咱們預期,然而7和8則超出了咱們預期的鎖範圍。爲何會超出咱們預期呢?這次咱們進行分析一下:

從七、8插入語句來看,因爲id爲自增主鍵,會自動遞增,語句7插入值預計爲:10_141;

語句8插入值預計爲:40_141,爲何只有後者能插入呢?
其實咱們能夠將B+樹中的間隙理解得更加精準一點:

age=20的三個間隙應該爲:(10_110, 20_120)、(20_120, 20_130)、(20_130, 40_140);

從上圖能夠看出語句7插入值10_141 沒法插入,由於間隙被鎖住了;而語句8插入 40_141值由於在間隙以外了,無鎖衝突,容許插入。

因此最終的加鎖狀況應該這樣表示:

  • age索引記錄鎖(Record Lock) :20_120, 20_130
  • age索引間隙鎖(Gap X-Lock):(10_110, 20_120)、(20_120, 20_130)、(20_130, 40_140)
  • 聚簇索引上的記錄鎖(Record X-Lock):id=120/130對應的行記錄

四、無索引

delete from t_user where address='a20',由於沒法精準定位,InnoDB選擇將聚簇索引中的全部行以及間隙都鎖起來,功能上已經等於鎖表了:

SQL驗證:

五、結論

InnoDB 在RC(READ COMMITTED)隔離級別中,只會在對應的索引/行記錄上加Record Lock,而不會加Gap鎖,緣由也很簡單,由於該隔離級別是容許存在幻讀問題的。

在RR級別下的加鎖方式稱之爲Next-Key Locks,其實就是上述Record Locks和Gap Locks的結合。好比Gap Lock爲(10,20) ,record lock爲20,結合的Next-Key lock 爲:(10, 20]。

分析Next-Key Locks其實就是要分析Record Locks和Gap Locks。MySQL InnoDB的可重複讀並不保證避免幻讀,須要應用使用加鎖讀來保證。而這個加鎖讀使用到的機制就是next-key locks。

若是使用普通的讀,會獲得一致性的結果,若是使用了加鎖的讀,就會讀到「最新的」「提交」讀的結果。自己,可重複讀和提交讀是矛盾的。在同一個事務裏,若是保證了可重複讀,

就會看不到其餘事務的提交,違背了提交讀;若是保證了提交讀,就會致使先後兩次讀到的結果不一致,違背了可重複讀。能夠這麼講,InnoDB提供了這樣的機制,在默認的可重複讀的隔離級別裏,可使用加鎖讀去查詢最新的數據。

更多內容敬請關注 vivo 互聯網技術 微信公衆號

注:轉載文章請先與微信號:labs2020 聯繫。

相關文章
相關標籤/搜索