記一次插入意向鎖和Next-Key引發的死鎖

說明:html

(1)爲方便測試,如下測試均使用test 測試表,其表結構爲:mysql

Table: test
Create Table: CREATE TABLE `test` (
  `id` bigint(20) NOT NULL,
  `oid` bigint(20) NOT NULL,
  `status` tinyint(2) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_oid` (`oid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

(2)如下事物隔離級別均爲RRsql

1、插入意向鎖

1.1 認識插入意向鎖

官方介紹:數據庫

Insert Intention Locks併發

插入若干數據以下高併發

mysql> select * from test;
+----+-----+--------+
| id | oid | status |
+----+-----+--------+
|  1 |   1 |      0 |
|  2 |   2 |      0 |
|  5 |   5 |      0 |
| 10 |  10 |      0 |
+----+-----+--------+

(1)按時序分別執行兩個事物:性能

time trx_a trx_b
time_1 start transaction;
time_2 start transaction;
time_3 select * from test where oid = 5 for update ;
time_4 insert into test set id = 3, oid = 3; (鎖等待, 插入意向鎖)

(2)show engine innodb status :測試

------------
TRANSACTIONS
------------
Trx id counter 57450
Purge done for trx's n:o < 57448 undo n:o < 0 state: running but idle
History list length 716
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 281479621176896, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 57449, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 123145316274176, query id 277 localhost root update
insert into test set id = 3, oid = 3
------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1089 page no 4 n bits 80 index idx_oid of table `test`.`test` trx id 57449 lock_mode X locks gap before rec insert intention waiting
//插入意向鎖


Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000005; asc         ;;
 1: len 8; hex 8000000000000004; asc         ;;

------------------
---TRANSACTION 57448, ACTIVE 8 sec
4 lock struct(s), heap size 1136, 5 row lock(s)
MySQL thread id 4, OS thread handle 123145316552704, query id 276 localhost root cleaning up

其中 gap before rec insert intention waiting 即爲插入意向鎖spa

1.2 插入意向鎖的特性與做用

論 MySql InnoDB 如何經過插入意向鎖控制併發插入 介紹了插入意向鎖的主要做用:提高併發插入能力.net

插入意向鎖本質上能夠當作是一個Gap Lock

  • 普通的Gap Lock 不容許 在 (上一條記錄,本記錄) 範圍內插入數據
  • 插入意向鎖Gap Lock 容許 在 (上一條記錄,本記錄) 範圍內插入數據

插入意向鎖的做用是爲了提升併發插入的性能, 多個事務 同時寫入 不一樣數據 至同一索引範圍(區間)內,並不須要等待其餘事務完成,不會發生鎖等待

可是須要注意:須要強調的是,雖然插入意向鎖中含有意向鎖三個字,可是它並不屬於意向鎖而屬於間隙鎖,由於意向鎖表鎖插入意向鎖行鎖

1.3 插入意向鎖和其餘鎖的兼容性

Mysql鎖詳解(行鎖、表鎖、意向鎖、Gap鎖、插入意向鎖)給出了鎖介紹及鎖兼容狀況,及常見的插入意向鎖衝突。

注1:兼容性和加鎖順序有關係,所以兼容性表格不是對稱的

注2:此表格閱讀方式爲按列讀

是否兼容 gap insert intention record next-key
gap
insert intention
record
next-key

從圖中能夠看出, 若是前一個事務 持有 gap 鎖, 或者 next-key 鎖的時候,後一個事務若是想要持有 insert intention 鎖的時候會 不兼容,出現鎖等待

2、死鎖分析

從1.3節能夠看出, 關於 insert intention 的鎖等待有兩種狀況

case1: 事務a 得到 gap lock; 事務 b insert intention 等待

case2: 事務a 得到next-key lock; 事務 b insert intention 等待;

2.1 死鎖復現

最近線上出現了死鎖,緣由就是基於case2, 和 mysql併發insert死鎖問題——gap、插入意向鎖衝突 有一點相似, 可是本人遇到是的,update 和 insert 操做 在兩個事務併發的時候 死鎖,具體以下:

事務開始前,數據狀況以下:

mysql> select * from test;
+----+-----+--------+
| id | oid | status |
+----+-----+--------+
|  1 |   1 |      0 |
|  2 |   2 |      0 |
|  5 |   5 |      0 |
| 10 |  10 |      0 |
+----+-----+--------+

按照時序進行操做,出現死鎖,以下:

time trx_a trx_b
time_1 start transaction;
time_2 start transaction;
time_3 update test set status = 1 where oid = 5;
time_4 update test set status = 1 where oid = 5; (鎖等待)
time_5 insert into test set id = 4, oid = 5; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

此時出現死鎖,截圖以下:

deadlock1.png
clipboard.png

執行 show engine innodb status , 找到對應的 LATEST DETECTED DEADLOCK 以下(附部分解析):

------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-07-08 19:55:23 0x700000d95000
*** (1) TRANSACTION:
TRANSACTION 57432, ACTIVE 44 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 3, OS thread handle 123145316274176, query id 176 localhost root updating

update test set status = 1 where oid = 5
//事務b操做語句

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 1089 page no 4 n bits 72 index idx_oid of table `test`.`test` trx id 57432 lock_mode X waiting
//RECORD LOCKS 表示記錄鎖,space id爲1089,page號4 ,n bits 72表示這個彙集索引記錄鎖結構上留有72個Bit位
//表示事務1 正在等待表 test 上的 idx_oid 的 X 鎖; 本案例中實際上是Next-Key lock

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000005; asc         ;;
 1: len 8; hex 8000000000000005; asc         ;;

*** (2) TRANSACTION:
TRANSACTION 57431, ACTIVE 59 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 4, OS thread handle 123145316552704, query id 177 localhost root update

insert into test set id = 4, oid = 5
//事務a 的操做sql

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 1089 page no 4 n bits 72 index idx_oid of table `test`.`test` trx id 57431 lock_mode X
//顯示了事務2 insert into test set id = 4, oid = 5 持有了a=5 的Lock mode X |LOCK_GAP 


Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000005; asc         ;;
 1: len 8; hex 8000000000000005; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 1089 page no 4 n bits 72 index idx_oid of table `test`.`test` trx id 57431 lock_mode X locks gap before rec insert intention waiting
//表示事務2的insert 語句正在等待插入意向鎖 lock_mode X locks gap before rec insert intention waiting (LOCK_X + LOCK_REC_GAP )
這裏須要各位注意的是鎖組合,相似lock_mode X waiting ,lock_mode X,lock_mode X locks gap before rec insert intention waiting 是咱們分析死鎖的核心重點。


Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000005; asc         ;;
 1: len 8; hex 8000000000000005; asc         ;;

*** WE ROLL BACK TRANSACTION (1)

如何閱讀死鎖日誌 有較爲詳細的介紹瞭如何解讀死鎖日誌;關鍵解析以下:

(1)事務b解析

update test set status = 1 where oid = 5
//事務b操做語句

* (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 1089 page no 4 n bits 72 index idx_oid of table test.test trx id 57432 lock_mode X waiting
//RECORD LOCKS 表示記錄鎖,space id爲1089,page號4 ,n bits 72表示這個彙集索引記錄鎖結構上留有72個Bit位
//表示事務b 正在等待表 test 上的 idx_oid 的 X 鎖; 本案例中實際上是Next-Key lock

(2)事務a解析

insert into test set id = 4, oid = 5
//事務a 的操做sql

* (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 1089 page no 4 n bits 72 index idx_oid of table test.test trx id 57431 lock_mode X
//顯示了事務2 insert into test set id = 4, oid = 5 持有了a=5 的Lock mode X |LOCK_GAP

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 8000000000000005; asc ;;
1: len 8; hex 8000000000000005; asc ;;

* (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 1089 page no 4 n bits 72 index idx_oid of table test.test trx id 57431 lock_mode X locks gap before rec insert intention waiting
//表示事務2的insert 語句正在等待插入意向鎖 lock_mode X locks gap before rec insert intention waiting (LOCK_X + LOCK_REC_GAP )
這裏須要各位注意的是鎖組合,相似lock_mode X waiting ,lock_mode X,lock_mode X locks gap before rec insert intention waiting 是咱們分析死鎖的核心重點。

具體分析以下:

time trx_a trx_b
time_1 start transaction;
time_2 start transaction;
time_3 update test set status = 1 where oid = 5;

因爲oid=5 的數據存在,申請得到 next-key lock, 申請成功;
time_4 update test set status = 1 where oid = 5; (鎖等待)

因爲oid=5 的數據存在, 申請獲取next-key lock, 可是此時已經有 next-key lock 所以等待中;
time_5 insert into test set id = 4, oid = 5;

申請插入意向鎖,可是已經有個next-key lock存在,所以等待中;詳詢1.3 中的兼容性
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

所以互相等待,行成環形等待

2.2 注意點

上述時序的兩個併發操做並不必定死鎖。

如:

事務開始前,數據狀況以下:

mysql> select * from test;
+----+-----+--------+
| id | oid | status |
+----+-----+--------+
|  1 |   1 |      0 |
|  2 |   2 |      0 |
|  5 |   5 |      0 |
| 10 |  10 |      0 |
+----+-----+--------+

按照時序進行操做,不會出現死鎖,以下:

time trx_a trx_b
time_1 start transaction;
time_2 start transaction;
time_3 update test set status = 1 where oid = 5;
time_4 update test set status = 1 where oid = 5; (鎖等待)
time_5 insert into test set id = 6, oid = 5; //(注意,此時id = 6 , 大於5) Query OK, 1 row affected

緣由,插入的主鍵id 大於 oid 對應的 主鍵id;

(如下屬於我的理解)因爲next-key lock 中的gap lock 能夠分解爲 兩個部分,(pre, 5)和 (5,next), pre 和 next 分別爲 oid 索引的 數據爲5 時的前一條數據和後一條數據;等待上鎖的時候,先等待給(pre,5)上鎖,其對應的主鍵id 小於 oid = 5 時的數據的主鍵id 5; 因此當事務1插入 主鍵id 大於5 的數據時,不會形成鎖等待;另插入意向鎖實則爲gap鎖,當 主鍵id 大於5 的數據時,此時的gap 區間爲 (5,next), 而事務b 此時在等待給(pre, 5)上鎖,所以不會產生鎖等待,故執行成功。

3、小結

發生死鎖的時候是個很好的提高數據庫能力的時候,總結解決思路:

(1)根據show engine innodb status , 找出對應的死鎖sql,進而找到對應的事務完整執行狀況;

(2)尤爲注意 當已存在 gap / next-key 時, 申請 insert intention 產生的鎖等待;

(3)藉助 INNODB_LOCKS 、INNODB_LOCK_WAITS 等表數據進一步分析。

4、參考

[1] 如何閱讀死鎖日誌

[2] mysql併發insert死鎖問題——gap、插入意向鎖衝突

[3] Mysql鎖詳解(行鎖、表鎖、意向鎖、Gap鎖、插入意向鎖)

相關文章
相關標籤/搜索