MySQL innodb的鎖機制解讀

網上有許多關於innodb的鎖機制的文章,有許多文章講述的不明白或者有問題,最近研究了很久,結合網上資料和實踐操做,記錄一下,供你們參考。若是有不對的地方,請隨時留言。數據庫

一 Innodb具有的鎖種類spa

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

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

二 Innodb內部實現的鎖種類io

1. 記錄鎖innodb

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

2. 間隙鎖date

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

例如: 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;

相關文章
相關標籤/搜索