死鎖案例一

1、環境說明

MySQL5.6.33,隔離級別是RR。表結構及數據:

CREATE TABLE `t0` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c` int(11) NOT NULL DEFAULT '0',
  `d` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `c` (`c`,`d`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8

mysql> select *from t0;
+----+----+---+
| id | c  | d |
+----+----+---+
|  1 |  1 | 0 |
|  2 |  3 | 0 |
|  3 |  5 | 0 |
|  4 |  7 | 0 |
|  5 | 10 | 0 |
|  6 | 12 | 0 |
|  7 | 14 | 0 |
|  8 | 16 | 0 |
+----+----+---+
8 rows in set (0.00 sec)

2、測試用例

會話1 會話 2
begin; begin;
update t0 set d=1 where c=6;
update t0 set d=1 where c in(5,10);
update t0 set d=1 where c=7;
deadlock

3、死鎖日誌

------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-06-30 18:50:57 a3445b90
*** (1) TRANSACTION:
TRANSACTION 7486, ACTIVE 21 sec updating or deleting
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 320, 7 row lock(s), undo log entries 1
MySQL thread id 7, OS thread handle 0xa3414b90, query id 183 localhost root updating
update t0 set d=1 where c in(5,10)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 16 page no 4 n bits 80 index `c` of table `yzs`.`t0` trx id 7486 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000007; asc     ;;
 1: len 4; hex 80000000; asc     ;;
 2: len 4; hex 80000004; asc     ;;

*** (2) TRANSACTION:
TRANSACTION 7485, ACTIVE 59 sec updating or deleting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 320, 5 row lock(s), undo log entries 1
MySQL thread id 6, OS thread handle 0xa3445b90, query id 184 localhost root updating
update t0 set d=1 where c=7
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 16 page no 4 n bits 80 index `c` of table `yzs`.`t0` trx id 7485 lock_mode X locks gap before rec
Record lock, heap no 5 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000007; asc     ;;
 1: len 4; hex 80000000; asc     ;;
 2: len 4; hex 80000004; asc     ;;

Record lock, heap no 6 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 4; hex 80000000; asc     ;;
 2: len 4; hex 80000005; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 16 page no 4 n bits 80 index `c` of table `yzs`.`t0` trx id 7485 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 4; hex 80000000; asc     ;;
 2: len 4; hex 80000005; asc     ;;

*** WE ROLL BACK TRANSACTION (2)

4、分析死鎖日誌

TRANSACTION 7485事務:

對二級索引(7,0,4)獲取X類型的gap鎖;

對二級索引(10,0,5)獲取X類型的gap鎖

等待X類型的插入意向鎖,對(10,0,5)

TRANSACTION 7486事務:

等待X類型的插入意向鎖,對(7,0,4)

5、加鎖原理
死鎖案例一
6、解析
1)會話2事務執行update t0 set d=1 where c=6;首先進入search階段,row_search_for_mysql調用btr_pcur_open_with_no_init從二級索引中搜索c=6;(搜索模式是>=6)。搜索出的返回結果是(7,0,4),不等於6,進入判斷條件事務的隔離級別是RR並且沒有設置innodb_locks_unsafe_for_binlog,所以調用err = sel_set_rec_lock(btr_pcur_get_block(pcur),rec, index, offsets,prebuilt->select_lock_type, LOCK_GAP,thr);對二級索引(7,0,4)加LOCK_GAP鎖。因爲沒有找到對應的二級索引值,所以沒有進入update階段就返回了。

2)會話1事務執行update t0 set d=1 where c in(5,10);

  首先,進入search階段,二級索引搜索5。row_search_for_mysql調用btr_pcur_open_with_no_init從二級索引中搜索c=5;(搜索模式>=5),搜索出的結果集是(5,0,3),等於5,進入右邊流程,鎖類型是 lock_type = LOCK_ORDINARY;,調用err = sel_set_rec_lock(btr_pcur_get_block(pcur),rec, index, offsets,prebuilt->select_lock_type,lock_type, thr);對二級索引記錄(5,0,3)加LOCK_ORDINARY鎖。然後重新調用row_search_for_mysql->btr_pcur_move_to_next(pcur, &mtr)檢索next record,next record是(7,0,4),不等於5,對(7,0,4)加 LOCK_GAP鎖。

  其次,search階段,二級索引繼續搜10。同理對(10,0,5)加LOCK_ORDINARY鎖,對(12,0,6)加LOCK_GAP鎖。

然後,search階段,搜索聚集索引3,即二級索引記錄(5,0,3)解析出的聚集索引。row_search_for_mysql調用調用btr_pcur_open_with_no_init搜索返回的結果集是(3,5,0),等於3,所以鎖類型lock_type改爲LOCK_REC_NOT_GAP,調用err = sel_set_rec_lock(btr_pcur_get_block(pcur),rec, index, offsets,prebuilt->select_lock_type,lock_type, thr);對聚集索引記錄(3,5,0)加LOCK_REC_NOT_GAP鎖。(注:後面因爲會本地更新,所以加鎖的聚集索引記錄是(3,5,1))

 進入update階段。調用函數row_update_for_mysql->row_upd_step->row_upd->row_upd_clust_step

                                                                                                                            ->row_upd_sec_step

先對聚集索引記錄進行處理,然後對二級索引處理。row_upd_clust_step調用流程一直向右流動,最終調用row_upd_clust_rec->btr_cur_optimistic_update->btr_cur_update_in_place對聚集索引就本地更新,不會再加鎖(search階段以及對其加了鎖了)。然後對二級索引處理,調用row_upd_sec_step->row_upd_sec_index_entry->btr_cur_del_mark_set_sec_rec->lock_sec_rec_modify_check_and_lock->err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP,block, heap_no, index, thr); 對二級索引記錄(5,0,3)在刪除前先加X類型的LOCK_REC_NOT_GAP鎖,判斷已經對其加了更強的鎖next-key鎖,所以就不用加了;然後接着執行->row_ins_sec_index_entry->row_ins_sec_index_entry_low-> btr_cur_optimistic_insert->btr_cur_ins_lock_and_undo判斷加插入意向鎖在(7,0,4)。(因爲會話2已經在(7,0,4)加上了gap鎖,所以這裏需要申請插入意向鎖),插入意向鎖和gap鎖互斥,所以會等待申請該插入意向鎖。

同理,search階段搜索聚集索引記錄5,並update階段處理二級索引。search階段對聚集索引(5,10,0)即(5,10,1)加LOCK_REC_NOT_GAP鎖,對二級索引記錄(10,0,5)不用再加鎖。插入(10,1,5),沒有gap鎖限制,插入成功。

3)會話1事務繼續執行update t0 set d=1 where c=7;

  同理,對二級索引記錄(7,0,4)加next key鎖,對(10,0,5)加gap鎖;對聚集索引記錄(4,7,0)加LOCK_REC_NOT_GAP鎖;然後插入二級索引記錄(7,1,4)時,對(10,0,5)申請插入意向鎖,因爲會話2對(10,0,5)已結加了next key鎖,所以進入鎖等待。

4)此時已經分析出,會話1等待申請(10,0,5)的插入意向鎖,擁有(7,0,4)的gap鎖;會話2擁有(10,0,5)的next key鎖,等待申請(7,0,4)的插入意向鎖,發生死鎖。

7、解決方法

解決方法比較簡單,把組合索引(c,d)中的d去掉,改爲只對c建立索引,即不更改二級索引記錄來避免gap/next-key鎖阻塞二級索引記錄上的插入意向鎖。

8、參考

https://mp.weixin.qq.com/s/b9gNbdEHV3NNQrV9PKDPSw

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章