1. 程式人生 > 實用技巧 >innodb事務鎖

innodb事務鎖

innodb事務鎖

根據文件innodb鎖分為以下幾種:

  • 意向鎖:

    就是簡單的IX,IS這類意向鎖,這個容易理解,比如要在表上讀取一行記錄,那麼表可能會被加IS鎖,在表上寫入就會被加IX鎖

  • 行鎖:
    這個也非常容易理解,就是在行上面加鎖,S鎖或者X鎖

  • gap鎖:
    gap鎖就是為了在repeatable read隔離級別下消除幻讀引入的一種鎖,打到serializable隔離級別的效果

  • next-key鎖:
    其實這個鎖是有gap鎖和record鎖組成的,LOCK_ORDINARY 表示next-key lock 但是在8.0中已經看不出來了。

  • 插入意向鎖(INSERT_INTENTION)
    插入意向鎖是gap鎖的一種,2個插入意向所gap鎖並不會產生衝突。

repeatable read 隔離級別

在這個隔離級別下,innodb為了實現repeatable read消除幻讀,達到serializable的效果就引入了gap lock。

next-key lock和gap lock

LOCK_ORDINARY 表示next-key lock 但是在8.0中已經看不出來了,在performance_schema.data_locks中是gap鎖和record2條記錄:

 begin;
 update id = 45 where i=40;

在另外一個會話中執行:

 select thread_id,engine_transaction_id,object_name,index_name,lock_type,lock_mode,lock_status,lock_data from data_locks;
thread_id engine_transaction_id object_name index_name lock_type lock_mode lock_status lock_data
240089 5446 l NULL TABLE IX GRANTED NULL
240089 5446 l i RECORD X GRANTED 40, 50
240089 5446 l PRIMARY RECORD X,REC_NOT_GAP GRANTED 50
240089 5446 l i RECORD X,GAP GRANTED 60, 60
240089 5446 l i RECORD X,GAP GRANTED 40, 45

第2條和第5條組合其實就是一個next-key lock
第3條鎖定primary 50 是因為原來的id=50

插入意向鎖(INSERT_INTENTION lock)

在repeatable read隔離級別下,insert會先去判斷一下 insert intention lock若成功,插入然後釋放。且2個插入意向鎖在同一個gap中並不會堵塞測試也很簡單在上面事務的基礎上執行語句:

insert into l values(60,35);

檢視鎖資訊:

thread_id engine_transaction_id object_name index_name lock_type lock_mode lock_status lock_data
240289 5447 l NULL TABLE IX GRANTED NULL
240289 5447 l PRIMARY RECORD S,REC_NOT_GAP GRANTED 60
240289 5447 l i RECORD X,GAP,INSERT_INTENTION WAITING 40, 45
240089 5446 l NULL TABLE IX GRANTED NULL
240089 5446 l i RECORD X GRANTED 40, 50
240089 5446 l PRIMARY RECORD X,REC_NOT_GAP GRANTED 50
240089 5446 l i RECORD X,GAP GRANTED 60, 60
240089 5446 l i RECORD X,GAP GRANTED 40, 45

而執行成功後鎖會變成:

thread_id engine_transaction_id object_name index_name lock_type lock_mode lock_status lock_data
240289 5454 l NULL TABLE IX GRANTED NULL

也就是說插入意向鎖已經被釋放。

在2個會話中分別執行,發現並不會堵塞執行成功:

 begin;
  insert into l values(52,51);
 begin;
 insert into l values(51,50);

鎖資訊:

thread_id engine_transaction_id object_name index_name lock_type lock_mode lock_status lock_data
240289 5454 l NULL TABLE IX GRANTED NULL
240089 5453 l NULL TABLE IX GRANTED NULL

read commit 隔離級別

因為innodb的特點,mysql的update並不會堵塞select語句。除非顯示的提示加共享鎖。
在read commit隔離級別下:

begin;
update l set id = 45 where i=40;
thread_id engine_transaction_id object_name index_name lock_type lock_mode lock_status lock_data
240089 5455 l NULL TABLE IX GRANTED NULL
240089 5455 l i RECORD X,REC_NOT_GAP GRANTED 40, 50
240089 5455 l PRIMARY RECORD X,REC_NOT_GAP GRANTED 50

和之前的相比上了lock_mode 少了帶gap的標記,原先的x鎖中也加了rec_not_gap。也就是說read commit不再鎖定資料的gap。

堵塞判斷

innodb堵塞判斷因為有了gap,next-key,insert intention鎖變的複雜(lock_rec_has_to_wait函式中體現):

  1. 首選判斷意向鎖,為此還定義了一個堵塞矩陣:

 static const byte lock_compatibility_matrix[5][5] = { 
    /**         IS        IX         S          X           AI */ 
    /* IS */ { TRUE, TRUE, TRUE, FALSE, TRUE}, 
    /* IX */ { TRUE, TRUE, FALSE, FALSE, TRUE}, 
    /* S */ { TRUE, FALSE, TRUE, FALSE, FALSE}, 
    /* X */ { FALSE, FALSE, FALSE, FALSE, FALSE}, 
    /* AI */ { TRUE, TRUE, FALSE, FALSE, FALSE} };
  1. 即使步驟1衝突,根據不同的lock_mode 開始判斷,以下情況不需要等待:

    • 如果請求鎖住的是 supremum 或者 LOCK_GAP 為 1 並且 LOCK_INSERT_INTENTION 為 0
    • 如果請求鎖 LOCK_INSERT_INTENTION 為 0 並且已有鎖是 LOCK_GAP 為 1
    • 如果請求鎖 LOCK_GAP 為 1,請求鎖 LOCK_REC_NOT_GAP 為 1
    • 如果已有鎖 LOCK_INSERT_INTENTION 為 1

參考:

MySQL · 引擎特性 · InnoDB 事務鎖系統簡介
MySQL · 引擎特性 · Innodb 鎖子系統淺析