原文地址InnoDB Locking
本章節(jié)描述了InnoDB中使用的鎖.
- 共享鎖和排它鎖
- 意向鎖
- 記錄鎖
- 間隙鎖
- Next-Key鎖
- 插入意向鎖
- AUTO-INC 鎖
共享鎖和排它鎖
InnoDB實(shí)現(xiàn)了標(biāo)準(zhǔn)的行級別鎖,包括兩種類型:共享鎖和排它鎖
- 獲取到共享鎖(S)的事務(wù)可以讀取這一行。
- 獲取到排它鎖(X)的事務(wù)可以更改或刪除這一行。
如果事務(wù)T1拿到了第r行的S鎖,那么另外一個事務(wù)T2對第r行的鎖請求將會被如下處理:
- 如果T2請求獲取共享鎖,那么可以立即成功。結(jié)果是T1和T2都拿到了第r行的共享鎖。
- 如果T2請求獲取排他鎖,那么將不會立即成功。
如果T1拿到了第r行的排它鎖,那么T2無論請求哪個類型的鎖,都不會立即成功。T2必須等待T1釋放第r行的鎖,才有可能拿到鎖。
意向鎖
InnoDB支持多個粒度的鎖,它允許行級別鎖和表鎖同時存在。為了實(shí)現(xiàn)這一點(diǎn),InnoDB使用了額外的鎖,叫做意向鎖。意向鎖是表級別的鎖,當(dāng)事務(wù)使用某種類型的意向鎖時,說明事務(wù)接下來要請求表中的某一行的同類型鎖。InnoDB中使用了兩種類型的意向鎖:
- 意向共享鎖(IS): 事務(wù)接下來要對表中某一行加共享鎖。
- 意向排它所(IX): 事務(wù)接下來要對表中某些行加排它鎖。
舉例來說,SELECT ... LOCK IN SHARE MODE加了意向共享鎖,SELECT ... FOR UPDATE加了意向排它鎖。
意向鎖工作機(jī)制如下:
- 事務(wù)在獲取表t的第r行的共享鎖之前,必須先獲取t的IS鎖,或者更強(qiáng)的鎖。
- 事務(wù)在獲取表t的第r行的排它鎖之前,必須先獲取t的IX鎖。
| X | IX | S | IS | |
|---|---|---|---|---|
| X | 沖突 | 沖突 | 沖突 | 沖突 |
| IX | 沖突 | 兼容 | 沖突 | 兼容 |
| S | 沖突 | 沖突 | 兼容 | 兼容 |
| IS | 沖突 | 兼容 | 兼容 | 兼容 |
這些規(guī)則可以總結(jié)為一個二維的鎖兼容性矩陣,如下:
| X | IX | S | IS | |
|---|---|---|---|---|
| X | 沖突 | 沖突 | 沖突 | 沖突 |
| IX | 沖突 | 兼容 | 沖突 | 兼容 |
| S | 沖突 | 沖突 | 兼容 | 兼容 |
| IS | 沖突 | 兼容 | 兼容 | 兼容 |
如果事務(wù)請求的鎖與當(dāng)前已存在的鎖兼容,那么事務(wù)將成功獲取鎖,否則將會等待,直到這個已存在的沖突的鎖被釋放為止。如果一個與當(dāng)前存在的鎖沖突的鎖請求成功了,那么將會導(dǎo)致死鎖錯誤。
因此,意向鎖只會阻塞要求獲取整個表的那種請求。IX和IS存在的主要目的是說明當(dāng)前有某個事務(wù)鎖住了一行數(shù)據(jù),或者將要鎖住一行數(shù)據(jù)。
使用SHOW ENGINE INNODB STATUS命令以及InnoDB monitor可以查看意向鎖,如下:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
記錄鎖
記錄鎖是加在索引記錄上的鎖。舉例來說,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;阻止了其他事務(wù)插入、修改或者刪除cl的值為10的這一行記錄。
記錄鎖總是鎖在索引上,即使這個表在定義時沒有定義索引。這種情況下,InnoDB會創(chuàng)建一個隱藏的組合索引,并使用這個索引來給記錄加鎖。(查看聚合索引和二級索引)
使用SHOW ENGINE INNODB STATUS命令以及InnoDB monitor可以查看記錄鎖,如下:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
間隙鎖
間隙鎖是加在一個間隙上的鎖,這個間隙在兩個索引之間,也可能在第一個索引之前或者最后一個索引之后。舉例來說,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;阻止了其他事務(wù)向表中插入一條c1的值為15的記錄,不管是否已經(jīng)存在這樣一條記錄,因?yàn)檫@個范圍之內(nèi)的所有記錄都被鎖住了。
一個間隙可能包括了一個索引值,或者包括多個,也可能一個都沒有。
間隙鎖是權(quán)衡了性能和并發(fā)能力的一個折中選擇,在某些隔離級別中使用了間隙鎖。
當(dāng)使用一個唯一索引去查找唯一的一行記錄時是用不上間隙鎖的(不包括那種使用多個列成員來作為查找條件,同時這些列被定義為唯一索引。這種情況下仍然會產(chǎn)生間隙鎖)。舉例來說,如果id這一列是唯一索引,那么如下的語句將會使用記錄鎖來鎖住id為100的記錄,而不關(guān)心是否有其他事務(wù)在之前的間隙中插入數(shù)據(jù):
SELECT * FROM child WHERE id = 100;
如果id不是索引,或者不是唯一索引,那么這條語句就會鎖住之前的間隙。
值得注意的是,兩種類型沖突的鎖是可以同時加在一個間隙上。舉例來說,事務(wù)A在一個間隙上加了一個共享間隙鎖(gap S-lock),同時事務(wù)B在同一個間隙上加了一個排它間隙鎖(gap X-lock)。這種情況是允許的,因?yàn)槿绻粭l記錄被刪除了,那么不同事務(wù)加在這個記錄左右兩邊的的間隙鎖必須合并(此處翻譯可能有誤)。
在InnoDB中,間隙鎖是"purely inhibitive"(不知如何翻譯),也就是說間隙鎖只會阻止其他事務(wù)往間隙中插入數(shù)據(jù)。間隙鎖不阻止不同的事務(wù)在同一個間隙上加鎖。因此,間隙排它鎖(gap X-lock)和間隙共享鎖(gap S-lock)的效果是一樣的。
可以顯式禁止間隙鎖。將事務(wù)隔離級別設(shè)置為READ COMMITTED或者將系統(tǒng)配置innodb_locks_unsafe_for_binlog設(shè)置生效(不推薦這么搞),可以禁止使用間隙鎖。這種情況下,間隙鎖只會用來做外鍵約束檢查和重復(fù)key檢查。
使用READ COMMITTED隔離級別或者將系統(tǒng)配置innodb_locks_unsafe_for_binlog設(shè)置生效還有其他兩個影響。當(dāng)MySQL計算完WHERE語句后,會釋放記錄鎖。對于UPDATE語句,InnoDB會做"半一致性(semi-consistent)"讀,它返回最近一次提交版本的記錄給MySQL,然后MySQL會決定這個記錄是否和UPDATE語句的WHERE條件匹配。(這段也不是很懂-_-||)
Next-Key鎖
Next-Key Lock是記錄鎖和間隙鎖的結(jié)合產(chǎn)物。
在查找和遍歷一個表的索引時,InnoDB會做行級別的鎖,它對遇到的索引記錄加上共享鎖或者排它鎖。因此行鎖實(shí)際上就是對索引加的鎖。在索引記錄上加的next-key lock會影響這個索引之前的間隙。意思就是,一個next-key lock是一個索引上的鎖外加這個索引之前那部分間隙的鎖。如果一個會話在記錄R的索引上加了共享鎖或者排它鎖,那么其他會話不能在這個記錄R之前(按照索引順序)的間隙中插入記錄。
假設(shè)索引中包含了10,11,13,20這幾個值。對于這個索引,可能的next-key lock會覆蓋如下幾個間隙,原括弧表示開區(qū)間,方括弧表示閉區(qū)間:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
對于最后一個間隙,next-key lock鎖住了最大的索引值之后的間隙,以及一個偽索引值,這個索引值比所有的索引都大。這個偽索引實(shí)際上是不存在的,所以nex-key lock只是鎖住了最大索引值后面的間隙。
默認(rèn)情況下,InnoDB工作在REPEATABLE READ級別下,同時innodb_locks_unsafe_for_binlog沒有設(shè)置為生效。在這種情況下,InnoDB在查找和遍歷索引時會使用next-key lock,也就防止了幻行(查看Phantom Rows)。
使用SHOW ENGINE INNODB STATUS命令以及InnoDB monitor可以查看next-key鎖,如下:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
插入意向鎖
插入意向鎖是一種間隙鎖,INSERT操作在插入行之前,會加一個插入意向鎖。這個鎖顯示了這樣一種意圖:在插入數(shù)據(jù)時,多個事務(wù)如果向同一個間隙中插入數(shù)據(jù),但是并不是插入同一個位置的數(shù)據(jù),那么這些事務(wù)就不需要互相等待對方完成。假設(shè)有索引值4和7,現(xiàn)在又兩個事務(wù)分別要插入5和6,那么這兩個事務(wù)各自給4和7之間的間隙加上插入意向鎖,然后給待插入的行上加排它鎖,他們不會阻塞彼此,因?yàn)椴迦氲男袥]有沖突。
下面的例子展示了事務(wù)在獲取行的排它鎖之前,先加上了插入意向鎖。這個例子包括了兩個客戶請求,A和B。
客戶A創(chuàng)建了一個有索引的表,插入了兩條數(shù)據(jù)(90和102),然后啟動一個事務(wù),給ID大于100的行加了排它鎖。這個排它鎖包括了在記錄102之前的間隙:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
客戶B啟動一個事務(wù),向這個間隙中插入記錄。這個事務(wù)在等待獲取排他鎖之前,會先獲取一個插入意向鎖。
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
使用SHOW ENGINE INNODB STATUS命令以及InnoDB monitor可以查看插入意向鎖,如下:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc " ;;
2: len 7; hex 9000000172011c; asc r ;;...
AUTO-INC 鎖
AUTO-INC鎖是一個特殊的表級別鎖,當(dāng)事務(wù)向一個帶有AUTO_INCREMENT列成員的表中插入數(shù)據(jù)時,會先加一個AUTO-INC鎖。最簡單的情況下,如果一個事務(wù)正在向表中插入數(shù)據(jù),那么其他的想要插入數(shù)據(jù)的事務(wù)必須等待,這樣前面那個事務(wù)插入的數(shù)據(jù)才會有一個連續(xù)的primary key。
配置項(xiàng)innodb_autoinc_lock_mode控制著AUTO-INC鎖使用的算法。允許你自己選擇如何在插入操作的可預(yù)期自增序列以及并行能力中進(jìn)行權(quán)衡折衷。