文章總共分爲五個部分:html
大而全版(五合一):InnoDB的鎖機制淺析(All in One)mysql
這一章節只列舉兩種死鎖場景,其餘的死鎖問題大多也萬變不離其宗。sql
示例的基礎是一個只有兩列的數據庫表。數據庫
mysql> CREATE TABLE test ( id int(11) NOT NULL, code int(11) NOT NULL, PRIMARY KEY(id), KEY (code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; mysql> INSERT INTO test(id,code) values(1,1),(10,10);
鎖的兼容矩陣以下:併發
--- | 排它鎖(X) | 意向排它鎖(IX) | 共享鎖(S) | 意向共享鎖(IS) |
---|---|---|---|---|
排它鎖(X) | N | N | N | N |
意向排它鎖(IX) | N | OK | N | OK |
共享鎖(S) | N | N | OK | OK |
意向共享鎖(IS) | N | OK | OK | OK |
併發條件下,惟一鍵索引衝突可能會致使死鎖,這種死鎖通常分爲兩種,一種是rollback
引起,另外一種是commit
引起。ui
rollback
引起的Duplicate key死鎖我命名爲insert-insert-insert-rollback死鎖rest
事務一 | 事務二 | 事務三 |
---|---|---|
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); Query OK, 1 row affected (0.01 sec) |
||
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 執行以後被阻塞,等待事務一 |
||
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 執行以後被阻塞,等待事務一 |
||
mysql>rollback; Query OK, 0 rows affected (0.00 sec) |
||
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction | ||
Query OK, 1 row affected (16.13 sec) |
當事務一執行回滾時,事務二和事務三發生了死鎖。InnoDB的死鎖檢測一旦檢測到死鎖發生,會自動失敗其中一個事務,所以看到的結果是一個失敗另外一個成功。code
爲何會死鎖?htm
死鎖產生的緣由是事務一插入記錄時,對(2,2)記錄加X鎖,此時事務二和事務三插入數據時檢測到了重複鍵錯誤,此時事務二和事務三要在這條索引記錄上設置S鎖,因爲X鎖的存在,S鎖的獲取被阻塞。
事務一回滾,因爲S鎖和S鎖是能夠兼容的,所以事務二和事務三都得到了這條記錄的S鎖,此時其中一個事務但願插入,則該事務指望在這條記錄上加上X鎖,然而另外一個事務持有S鎖,S鎖和X鎖互相是不兼容的,兩個事務就開始互相等待對方的鎖釋放,形成了死鎖。blog
事務二和事務三爲何會加S鎖,而不是直接等待X鎖
事務一的insert語句加的是隱式鎖(隱式的Record鎖、X鎖),可是其餘事務插入同一行記錄時,出現了惟一鍵衝突,事務一的隱式鎖升級爲顯示鎖。
事務二和事務三在插入以前判斷到了惟一鍵衝突,是由於插入前的重複索引檢查,此次檢查必須進行一次當前讀,因而非惟一索引就會被加上S模式的next-key鎖,惟一索引就被加上了S模式的Record鎖。
由於插入和更新以前都要進行重複索引檢查而執行當前讀操做,因此RR隔離級別下,同一個事務內不連續的查詢,可能也會出現幻讀的效果(但我的並不認爲RR級別下也會出現幻讀,幻讀的定義應該是連續的讀取)。而連續的查詢因爲都是讀取快照,中間沒有當前讀的操做,因此不會出現幻讀。
commit
引起的Duplicate key死鎖delete-insert-insert-commit死鎖
事務一 | 事務二 | 事務三 |
---|---|---|
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> delete from test where id=2; Query OK, 1 row affected (0.01 sec) |
||
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 執行以後被阻塞,等待事務一 |
||
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 執行以後被阻塞,等待事務一 |
||
mysql>commit; Query OK, 0 rows affected (0.00 sec) |
||
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction | ||
Query OK, 1 row affected (2.37 sec) |
這種狀況下產生的死鎖和insert-insert-insert-rollback
死鎖產生的原理一致。
通過以上分析,一條數據在插入時通過如下幾個過程:
假設數據表test.test
中存在(1,1)、(5,5)和(10,10)三條記錄。
select * from test where id>8 for update
,事務二要插入(9,9),此時先要獲取插入意向鎖,因爲事務一已經在對應的記錄和間隙上加了X鎖,所以事務二被阻塞,而且阻塞的緣由是獲取插入意向鎖時被事務一的X鎖阻塞。Duplicate key
的錯誤。若是存在惟一索引,且索引加鎖,等待鎖釋放。仍然是表test,當前表中的記錄以下:
mysql> select * from test; +----+------+ | id | code | +----+------+ | 1 | 1 | | 5 | 5 | | 10 | 10 | +----+------+ 3 rows in set (0.01 sec)
事務一 | 事務二 |
---|---|
begin; | begin; |
select * from test where id=5 for update; | select * from test where id=10 for update; |
insert into test values(7,7); | |
insert into test values(7,7); | |
Query OK, 1 row affected (5.03 sec) | |
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
使用show engine innodb status
查看死鎖狀態。前後出現lock_mode X locks gap before rec insert intention waiting
和lock_mode X locks gap before rec
字眼,是gap鎖和插入意向鎖的衝突致使的死鎖。
首先回顧一下兩個事務中的select ... for update
作了哪些加鎖操做。
code=5
時,首先會獲取code=5
的索引記錄鎖(Record鎖),根據以前gap鎖的介紹,會在前一個索引和當前索引之間的間隙加鎖,因而區間(1,5)之間被加上了X模式的gap鎖。除此以外RR模式下,還會加next-key鎖,因而區間(5,10]被加了next-key鎖;
code=5
的加鎖範圍是,區間(1,5)的gap鎖,{5}索引Record鎖,(5,10]的next-key鎖。即區間(1,10)上都被加上了X模式的鎖。code=10
的加鎖範圍是,區間(5,10)的gap鎖,{10}索引Record鎖,(10,+∞)的next-key鎖。由gap鎖的特性,兼容矩陣中衝突的鎖也能夠被不一樣的事務同時加在一個間隙上。上述兩個select ... for update
語句出現了間隙鎖的交集,code=5
的next-key鎖和code=10
的gap鎖有重疊的區域——(5,10)。
當事務一執行插入語句時,會先加X模式的插入意向鎖
,即兼容矩陣中的IX鎖。
可是因爲插入意向鎖要鎖定的位置存在X模式的gap鎖
。兼容矩陣中IX和X鎖是不兼容的,所以事務一的IX鎖會等待事務二的gap鎖釋放。
事務二也執行插入語句,與事務一一樣,事務二的插入意向鎖IX鎖會等待事務一的gap鎖釋放。
兩個事務互相等待對方先釋放鎖,所以出現死鎖。
除了以上給出的幾種死鎖模式,還有不少其餘死鎖的場景。
不管是哪一種場景,萬變不離其宗,都是因爲某個區間上或者某一個記錄上能夠同時持有鎖,例如不一樣事務在同一個間隙gap上的鎖不衝突;不一樣事務中,S鎖能夠阻塞X鎖的獲取,可是不會阻塞另外一個事務獲取該S鎖。這樣纔會出現兩個事務同時持有鎖,並互相等待,最終致使死鎖。
其中須要注意的點是,增、刪、改的操做都會進行一次當前讀操做,以此獲取最新版本的數據,並檢測是否有重複的索引。
這個過程除了會致使RR隔離級別下出現死鎖以外還會致使其餘兩個問題: