淺談Innodb的鎖實現

林學習發表於2018-05-08

目錄

一、知識準備

1.1、lock和latch

1.2、主鍵索引和輔助索引

1.3、事務的隔離級別

二、Innodb行鎖的三種演算法

2.1、主鍵索引如何加鎖

2.2、輔助索引(非唯一索引)如何加鎖

三、發生死鎖的幾種情況

四、個人小結


一、知識準備

1.1、lock和latch

lock::lock的物件是事務,用來鎖定的是資料庫中的物件,如表,頁,行。(這個概念很重要,有助於理解後續的加鎖行為

latch:latch是為了保證併發執行緒操作臨界資源的正確性(本文可以忽略這個概念)

1.2、主鍵索引和輔助索引

主鍵索引(聚集索引/聚簇索引): 每張表按照主鍵構造一棵b+樹,葉子節點存放的即為整張表的行記錄資料

輔助索引:按輔助索引的鍵值構建b+樹,葉子節點的索引行中,除了包含索引的鍵值外,還包含一個書籤(bookmark),指向主鍵索引上該行的完整記錄

1.3、事務隔離級別

此處不再贅述,本文會使用mysql預設的隔離級別,REPEATABLE READ(RR)

二、Innodb行鎖的三種演算法

Innodb有三種行鎖的演算法,分別是:

1⃣️、Record Lock:單個行記錄的鎖

2⃣️、Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身

3⃣️、Next-Key Lock:Record Lock + Gap Lock

下面我們分析下,不同場景下的加鎖情況,以及為什麼要用對應的加鎖演算法。

2.1、主鍵索引如何加鎖(mysql預設事務隔離級別:RR)

delete from t where id = 7; 這種情況加鎖很簡單,由於主鍵唯一,所以只需要在主鍵上id=7加X鎖即可。(Record Lock)


                                  圖1-1

2.2、輔助索引(非唯一索引)如何加鎖(mysql預設事務隔離級別:RR)

select * from t where id=5 for update; 該場景下的加鎖如下圖所示。

                                                      圖1-2

從圖中可以看出,本文除了在輔助索引和對應的主鍵索引上加了兩個行記錄的X鎖(Record Lock)外,還在輔助索引上加了3個GAP鎖。那麼為什麼要用GAP鎖呢?其實這個是Innodb為了解決幻讀問題(phantom problem)。大家想一下,為了保證一個事務中的連續兩次讀(如:select * from t where id=5 for update)結果相同,那麼就要防止上鎖期間,有id=5的記錄插入表中,由於id是非唯一索引,考慮到b+樹索引的連續性,所以,能夠插入id=5記錄的,只有(3,5),(5,5),(5,9)三個區間,即上圖三個紅箭頭的位置範圍(上文1.1中提到lock的物件是資料庫中的物件,所以GAP鎖的的邊界會加在具體的索引記錄上,所以id=5的話,那就會在3和9之間加上GAP鎖。)

網上有些資料,包括我看的一些書上,對於Next-Key Lock的描述要麼模凌兩可,要麼淺嘗則止。這樣的描述很容易誤導剛剛入坑的同學。大部分的資料描述的區間鎖範圍都是左閉右開。比如上文中select * from t where id=5 for update;那麼GAP LOCK的範圍就是 (3,5],(5,5],(5,9]。按照這個描述,那麼id=3的記錄不可插入,id=9的記錄可以插入。那麼,事實是不是這樣的呢?

下面我們一起來嘗試一下,直接上圖:

首先我們看下錶中的記錄(上文中的例子):


step1: sessionA ,發起一個事務,給記錄上鎖


setp2: sessionB,發起一個事務,插入邊界值。

插入id=3的兩條語句,一條失敗了,一條成功了,情況好像跟我們想的有點不一樣。。。疑問 。 繼續上圖


插入id=9的兩條語句,又是一條失敗了,一條成功了。這個情況,和Next-Key Lock的定義,左閉右開,貌似不一樣啊委屈

下面,我們一起來分析下,到底是為什麼?

要想搞明白這個問題,還是得提到1.1章節中,加粗的那個lock的定義,它是加在索引上的。再結合索引的連續性,那麼這個問題就好理解了大笑

請大家把目光回到圖1-2,對於輔助索引葉子節點上的排序,可以簡化為該圖中的樣式。所以,GAP LOCK的範圍是((3,d) ,(5,c)),((5,c) ,(5,f)),((5,f) ,(9,g)),RECORD LOCK鎖定的是(5,c)和(5,f)。所以對於邊界值的插入就很清楚了哈

場景1、

mysql> insert into t(id,name) values(3,'f');

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(3,f) 在((3,d) ,(5,c))之間,不能插入。

場景2、

mysql> insert into t(id,name) values(3,'b');

Query OK, 1 row affected (0.00 sec)

(3,b) 在((3,d) ,(5,c))的左側,可以插入。

場景3、

mysql> insert into t(id,name) values(9,'e');

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(9,e) 在((5,f) ,(9,g))之間,不能插入。

場景4、

mysql> insert into t(id,name) values(9,'h');

Query OK, 1 row affected (0.00 sec)

(9,h) 在((5,f) ,(9,g))右側,可以插入。


三、發生死鎖的幾種情況

3.1、顯示的互相等待(簡單場景)

3.1、隱式的互相等待(稍複雜場景)
還記得圖1-2中的場景嗎?輔助索引上加鎖後,會在對應的主鍵索引上加鎖。那麼當一張表中有多個輔助索引的時候,是不是會初現死鎖呢?這個場景作為思考題,大家自己思考下哈大笑

四、個人小結

本章的分享,其實就是我學習這塊知識遇到問題,以及解決問題的過程。想要理解innodb的加鎖原理,必須要理解b+樹和索引。當然了,遇到難題,不要輕易放棄哈,有時候用下逆向思維,想下作者發明這個技術,是為了解決什麼問題大笑







相關文章