目錄
一、知識準備
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 ,發起一個事務,給記錄上鎖
插入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))右側,可以插入。