MySQL innodb的鎖機制解讀


網上有許多關於innodb的鎖機制的文章,有許多文章講述的不明白或者有問題,最近研究了好久,結合網上資料和實踐操作,記錄一下,供大家參考。如果有不對的地方,請隨時留言。

一 Innodb具備的鎖種類

1. 表鎖(MySQL提供的,跟存儲引擎無關)

2. 行鎖(Innodb存儲引擎實現)

二 Innodb內部實現的鎖種類

1. 記錄鎖

對應Innodb的行鎖,記錄鎖鎖的是索引記錄,不是具體的數據記錄。

2. 間隙鎖

鎖定索引記錄間隙的鎖,確保索引記錄的間隙不變,間隙鎖是針對事務隔離等級是可重復讀或以上級別而言的!

例如: create table t1(id int, v1 int,v2 int, primary key(id), key `idx_v1` (`v1`)) engine=innodb;

數據行有以下幾行:

id v1 v2
1 1 0
2 3 1
3 4 2
5 5 3
7 7 4
10 9 5

 

 

 

 

 

 

間隙鎖一般是針對非唯一索引而言的

上面的數據表中v1的索引區間有

(-∞,1)

(1,3)

(3,4)

(4,5)

(5,7)

(7,9)

(9,+∞)

假如更新v1=5的數據行,那么此時會在索引記錄idx_v1加上間隙鎖,會把5之前的區間鎖定,鎖定的區間是(4,5)和(5,7),也就是區間(4,7),同時找到v1=5的數據行的主鍵,在該記錄上加上記錄鎖,鎖定該行記錄。

 

3. 后碼鎖

記錄鎖和間隙鎖的結合,對於innodb中,更新非唯一索引記錄時,會加上后碼鎖。如果更新記錄為空,就不能加記錄鎖,此時只剩下間隙鎖。多條更新語句可能導致不同事務中鎖定的索引區間重復,導致插入失敗。

例子(事務隔離等級為可重復讀,主要看一下后碼鎖是記錄鎖和間隙鎖的結合)

transaction 1 transaction 2
BEGIN; BEGIN;
update t1 set v2=1 where v1=6;(這句SQL會加上后碼鎖,但是v1=6的記錄不存在,后碼鎖是記錄鎖和間隙鎖組成的,此時只能加上間隙鎖,會鎖住idx_v1的索引區間是(5,7)) update t1 set v2=2 where v1=7;(這句SQL也是加上后碼鎖,v1=7的記錄存在,后碼鎖是由記錄鎖和區間鎖組成,首先會使用記錄鎖,鎖定v1=7的主鍵,即id=7的記錄行,同時會使用間隙鎖,會鎖住idx_v1的索引區間是(5,9))
INSERT INTO t1 set id=8, v1=6,v2=2;(v1=6在自己的idx_v1鎖定的索引區間(5,7)); INSERT INTO t1 set id=9, v1=8,v2=2;(v1=8在自己的idx_v1鎖定的索引區間(5,9));
鎖等待 插入成功
插入成功 rollback;
rollback;  

 

 

 

 

 

 

 

 

由此可以看出,一個索引區間是可以被多個間隙鎖鎖定的,更新不當的時候,會造成死鎖。

其中在transaction 2中,如果"INSERT INTO t1 set id=9, v1=8,v2=2;"這條語句換成"INSERT INTO t1 set id=9, v1=6,v2=2;"就會造成死鎖,因為兩個間隙鎖都鎖定了(5,7)這個區間。

同時在transaction 2中,執行update語句的時候,已經在id=7的主鍵索引上加了記錄鎖,任何在其他事務(例如transaction 1)中嘗試更新id=7的行,都會被掛起,直到transaction 2提交或回滾。

三  鎖選擇

       執行更新類語句,像SELECT ... FOR UPDATE, UPDATE,DELETE語句

   1.  如果更新條件沒有索引,例如執行"SELECT * FROM t1 where v2=2 for update",那么此時更新操作會使用表鎖。多條更新SQL語句在不同的事務中同時執行,先取得表鎖的事務會將其他事務掛起,直到當前事務提交或回滾。

       使用表鎖的原因:

       由於更新的數據沒有索引,MySQL只能做掃表操作,掃表的時候,要阻止其他任何的更新操作,所以會上升為表鎖。

       2. 如果更新條件為索引字段,但並非唯一索引(包括主鍵),例如執行"SELECT * FROM t1 where v1=1 for update",那么此時更新會使用后碼鎖。

   使用后碼鎖的原因:

   a)首先要保證在符合條件的記錄上加上排他的記錄鎖,會鎖定當前非唯一索引和這些滿足條件的記錄對應的主鍵索引;b)還要保證更新的索引記錄區間不能插入新數據。

       3. 如果更新條件字段為唯一索引,使用記錄鎖。

           Innodb會根據唯一索引,找到記錄的主鍵索引,將符合條件的主鍵索引和唯一索引加上記錄鎖。

      說明:Innodb的索引結構

      Innodb支持聚簇索引,但是聚簇索引只能是主鍵索引,並且每張表只能有一個聚簇索引,所謂聚簇索引,就是索引在物理存儲上是順序存放的。主鍵索引就是聚簇索引,主鍵索引的葉子節點存放的是記錄的物理地址,根據主鍵索引可以直接訪問記錄內容。非主鍵索引在B-tree索引的葉子節點上存放的並不是記錄的物理地址,而是主鍵索引的物理地址。

      當給非唯一索引加上后碼鎖的時候(例如更新非唯一主鍵索引對應的記錄內容),Innodb會采用后碼鎖。首先將滿足條件的非唯一索引對應的主鍵索引和滿足條件的非唯一索引加上記錄鎖。然后會給非唯一索引加上間隙鎖,將當前非唯一索引對應的索引區間加上間隙鎖,禁止在該區間的任何INSERT操作。

四 間隙鎖演示

    說明:加后碼鎖的時候,並未鎖住間隙兩端的記錄,那么兩端的記錄是可以更新的,但是如果更新記錄時會影響到間隙鎖,那需要被掛起,等待間隙鎖被釋放。

 

transaction 3 transaction 4
BEGIN; BEGIN;
SELECT * FROM t1 WHERE v1=5 FOR UPDATE;(加上間隙鎖,鎖定了idx_v1的索引區間是(4,7),同時會把滿足條件的記錄的主鍵索引上加上行鎖)

UPDATE 類操作

UPDATE t1 set v2=2 WHERE v1=4;(OK,不會被掛起,間隙鎖鎖只鎖間隙,而這條更新SQL並未影響idx_v1在區間(4,7)的間隙鎖控制范圍);

UPDATE t1 set v2=2 WHERE v1=7;(OK,不會被掛起)

UPDATE t1 set v2=2 WHERE v1=6;(OK,不會被掛起)

UPDATE t1 set v1=1 WHERE v1=4;(OK,不會被掛起,更新索引)

UPDATE t1 set v1=5 WHERE v1=4;(由於4,7區間被封鎖,這個操作會被掛起)

UPDATE t1 set v1=8 WHERE v1=7;(OK,不會被掛起,更新索引)

UPDATE t1 set v1=1 WHERE v1=9;(OK,不會被掛起,因為條件和目的索引的值都不在封鎖的區間)

UPDATE t1 set v1=5 WHERE v1=7;(由於4,7區間被封鎖,這個操作會被掛起)

UPDATE t1 set v1=2 WHERE v1=7(這個操作會被掛起)

由上面這兩組SQL可以看出來,間隙鎖鎖住的區間為4,7,當更新這兩端的記錄的時候,如果不改變區間的值,壓根就跟區間索引沒關系,那么更新操作就不會被間隙鎖掛起。如果更新間隙鎖區間的兩端的索引值,且更新索引后的區間包含當前鎖住的區間,那么可以更新成功。如果更新索引后,不能包含已經鎖定的區間,那么更新操作會被掛起。

INSERT 類操作

INSERT INTO t1 set id=11, v1=5, v2=5;(掛起,transaction 3封閉的idx_v1間隙是(4,7),插入v1=5肯定會被掛起)

INSERT INTO t1 set id=11, v1=8,v2=8(OK,v1不在封鎖區間);

主要看一下在封鎖區間兩端的插入情況

INSERT INTO t1 set id=4,v1=4,v2=2;(v1=4是封鎖區間的左側值,此操作會被掛起)

INSERT INTO t1 set id=0,v1=4,v2=2;(此操作OK,可以執行)

INSERT INTO t1 set id=6, v1=7,v2=2;(v1=7是封鎖區間的右側值,此操作會被掛起)

INSERT INTO t1 set id=8, v1=7,v2=2;(此操作OK,可以執行)

當往封邊區間兩端插入值的時候,需要看要插入的值的主鍵是否在封鎖區間對應的主鍵的范圍。

具體解釋:

id v1 v2
3 4 2
7 7 4

 

 

 

當插入左邊界值時,即插入v1=4的時候,要求主鍵id的值需要在小於id=3的范圍,當數據庫中v1=4左側值有多條記錄的時候,插入的id小於其中最大的id即可。

當插入右界值時,即插入v1=7的時候,要求主鍵id值大於id=7的范圍,當數據庫中v1=7右側值有多條記錄的時候,插入的id大於其中最小的id即可。

ROLLBACK;

ROLLBACK;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM