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函式中體現):
- 首選判斷意向鎖,為此還定義了一個堵塞矩陣:
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衝突,根據不同的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 鎖子系統淺析