原文地址InnoDB Locking
本章節(jié)描述了InnoDB中使用的鎖.
- 共享鎖和排它鎖
- 意向鎖
- 記錄鎖
- 間隙鎖
- Next-Key鎖
- 插入意向鎖
- AUTO-INC 鎖
共享鎖和排它鎖
InnoDB實現(xiàn)了標準的行級別鎖虽惭,包括兩種類型:共享鎖和排它鎖
- 獲取到共享鎖(S)的事務可以讀取這一行橡类。
- 獲取到排它鎖(X)的事務可以更改或刪除這一行。
如果事務T1拿到了第r行的S鎖芽唇,那么另外一個事務T2對第r行的鎖請求將會被如下處理:
- 如果T2請求獲取共享鎖顾画,那么可以立即成功。結(jié)果是T1和T2都拿到了第r行的共享鎖。
- 如果T2請求獲取排他鎖亲雪,那么將不會立即成功勇凭。
如果T1拿到了第r行的排它鎖,那么T2無論請求哪個類型的鎖义辕,都不會立即成功虾标。T2必須等待T1釋放第r行的鎖,才有可能拿到鎖灌砖。
意向鎖
InnoDB支持多個粒度的鎖璧函,它允許行級別鎖和表鎖同時存在。為了實現(xiàn)這一點基显,InnoDB使用了額外的鎖蘸吓,叫做意向鎖。意向鎖是表級別的鎖撩幽,當事務使用某種類型的意向鎖時库继,說明事務接下來要請求表中的某一行的同類型鎖。InnoDB中使用了兩種類型的意向鎖:
- 意向共享鎖(IS): 事務接下來要對表中某一行加共享鎖窜醉。
- 意向排它所(IX): 事務接下來要對表中某些行加排它鎖宪萄。
舉例來說,SELECT ... LOCK IN SHARE MODE加了意向共享鎖榨惰,SELECT ... FOR UPDATE加了意向排它鎖拜英。
意向鎖工作機制如下:
- 事務在獲取表t的第r行的共享鎖之前,必須先獲取t的IS鎖琅催,或者更強的鎖居凶。
- 事務在獲取表t的第r行的排它鎖之前,必須先獲取t的IX鎖藤抡。
X | IX | S | IS | |
---|---|---|---|---|
X | 沖突 | 沖突 | 沖突 | 沖突 |
IX | 沖突 | 兼容 | 沖突 | 兼容 |
S | 沖突 | 沖突 | 兼容 | 兼容 |
IS | 沖突 | 兼容 | 兼容 | 兼容 |
這些規(guī)則可以總結(jié)為一個二維的鎖兼容性矩陣侠碧,如下:
X | IX | S | IS | |
---|---|---|---|---|
X | 沖突 | 沖突 | 沖突 | 沖突 |
IX | 沖突 | 兼容 | 沖突 | 兼容 |
S | 沖突 | 沖突 | 兼容 | 兼容 |
IS | 沖突 | 兼容 | 兼容 | 兼容 |
如果事務請求的鎖與當前已存在的鎖兼容,那么事務將成功獲取鎖缠黍,否則將會等待舆床,直到這個已存在的沖突的鎖被釋放為止。如果一個與當前存在的鎖沖突的鎖請求成功了嫁佳,那么將會導致死鎖錯誤挨队。
因此,意向鎖只會阻塞要求獲取整個表的那種請求蒿往。IX和IS存在的主要目的是說明當前有某個事務鎖住了一行數(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;
阻止了其他事務插入、修改或者刪除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;
阻止了其他事務向表中插入一條c1的值為15的記錄,不管是否已經(jīng)存在這樣一條記錄丙笋,因為這個范圍之內(nèi)的所有記錄都被鎖住了谢澈。
一個間隙可能包括了一個索引值,或者包括多個御板,也可能一個都沒有锥忿。
間隙鎖是權(quán)衡了性能和并發(fā)能力的一個折中選擇,在某些隔離級別中使用了間隙鎖稳吮。
當使用一個唯一索引去查找唯一的一行記錄時是用不上間隙鎖的(不包括那種使用多個列成員來作為查找條件,同時這些列被定義為唯一索引井濒。這種情況下仍然會產(chǎn)生間隙鎖)灶似。舉例來說,如果id這一列是唯一索引瑞你,那么如下的語句將會使用記錄鎖來鎖住id為100的記錄酪惭,而不關(guān)心是否有其他事務在之前的間隙中插入數(shù)據(jù):
SELECT * FROM child WHERE id = 100;
如果id不是索引,或者不是唯一索引者甲,那么這條語句就會鎖住之前的間隙春感。
值得注意的是,兩種類型沖突的鎖是可以同時加在一個間隙上虏缸。舉例來說鲫懒,事務A在一個間隙上加了一個共享間隙鎖(gap S-lock),同時事務B在同一個間隙上加了一個排它間隙鎖(gap X-lock)刽辙。這種情況是允許的窥岩,因為如果一條記錄被刪除了,那么不同事務加在這個記錄左右兩邊的的間隙鎖必須合并(此處翻譯可能有誤)宰缤。
在InnoDB中颂翼,間隙鎖是"purely inhibitive"(不知如何翻譯)晃洒,也就是說間隙鎖只會阻止其他事務往間隙中插入數(shù)據(jù)。間隙鎖不阻止不同的事務在同一個間隙上加鎖朦乏。因此球及,間隙排它鎖(gap X-lock)和間隙共享鎖(gap S-lock)的效果是一樣的。
可以顯式禁止間隙鎖呻疹。將事務隔離級別設置為READ COMMITTED或者將系統(tǒng)配置innodb_locks_unsafe_for_binlog設置生效(不推薦這么搞)吃引,可以禁止使用間隙鎖艰管。這種情況下圆裕,間隙鎖只會用來做外鍵約束檢查和重復key檢查。
使用READ COMMITTED隔離級別或者將系統(tǒng)配置innodb_locks_unsafe_for_binlog設置生效還有其他兩個影響得运。當MySQL計算完WHERE
語句后姑蓝,會釋放記錄鎖鹅心。對于UPDATE
語句,InnoDB會做"半一致性(semi-consistent)"讀纺荧,它返回最近一次提交版本的記錄給MySQL旭愧,然后MySQL會決定這個記錄是否和UPDATE
語句的WHERE
條件匹配。(這段也不是很懂-_-||)
Next-Key鎖
Next-Key Lock是記錄鎖和間隙鎖的結(jié)合產(chǎn)物宙暇。
在查找和遍歷一個表的索引時输枯,InnoDB會做行級別的鎖,它對遇到的索引記錄加上共享鎖或者排它鎖占贫。因此行鎖實際上就是對索引加的鎖桃熄。在索引記錄上加的next-key lock會影響這個索引之前的間隙。意思就是型奥,一個next-key lock是一個索引上的鎖外加這個索引之前那部分間隙的鎖瞳收。如果一個會話在記錄R的索引上加了共享鎖或者排它鎖,那么其他會話不能在這個記錄R之前(按照索引順序)的間隙中插入記錄厢汹。
假設索引中包含了10,11,13,20這幾個值螟深。對于這個索引,可能的next-key lock會覆蓋如下幾個間隙烫葬,原括弧表示開區(qū)間界弧,方括弧表示閉區(qū)間:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
對于最后一個間隙,next-key lock鎖住了最大的索引值之后的間隙搭综,以及一個偽索引值垢箕,這個索引值比所有的索引都大。這個偽索引實際上是不存在的兑巾,所以nex-key lock只是鎖住了最大索引值后面的間隙舰讹。
默認情況下,InnoDB工作在REPEATABLE READ級別下闪朱,同時innodb_locks_unsafe_for_binlog沒有設置為生效月匣。在這種情況下钻洒,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ù)時,多個事務如果向同一個間隙中插入數(shù)據(jù)癣诱,但是并不是插入同一個位置的數(shù)據(jù)计维,那么這些事務就不需要互相等待對方完成。假設有索引值4和7撕予,現(xiàn)在又兩個事務分別要插入5和6鲫惶,那么這兩個事務各自給4和7之間的間隙加上插入意向鎖,然后給待插入的行上加排它鎖实抡,他們不會阻塞彼此欠母,因為插入的行沒有沖突。
下面的例子展示了事務在獲取行的排它鎖之前吆寨,先加上了插入意向鎖赏淌。這個例子包括了兩個客戶請求,A和B啄清。
客戶A創(chuàng)建了一個有索引的表六水,插入了兩條數(shù)據(jù)(90和102),然后啟動一個事務辣卒,給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啟動一個事務,向這個間隙中插入記錄添寺。這個事務在等待獲取排他鎖之前胯盯,會先獲取一個插入意向鎖懈费。
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鎖是一個特殊的表級別鎖,當事務向一個帶有AUTO_INCREMENT
列成員的表中插入數(shù)據(jù)時憎乙,會先加一個AUTO-INC鎖票罐。最簡單的情況下,如果一個事務正在向表中插入數(shù)據(jù)泞边,那么其他的想要插入數(shù)據(jù)的事務必須等待该押,這樣前面那個事務插入的數(shù)據(jù)才會有一個連續(xù)的primary key。
配置項innodb_autoinc_lock_mode控制著AUTO-INC鎖使用的算法阵谚。允許你自己選擇如何在插入操作的可預期自增序列以及并行能力中進行權(quán)衡折衷蚕礼。