發生死鎖了,如何排查和解決呢?本文將跟你一塊兒探討這個問題html
數據庫隔離級別:mysql
mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set, 1 warning (0.00 sec)
自動提交關閉:sql
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec)
表結構:數據庫
//id是自增主鍵,name是非惟一索引,balance普通字段 CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `balance` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
表中的數據:併發
開啓兩個終端模擬事務併發狀況,執行順序以及實驗現象以下:ide
1)事務A執行更新操做,更新成功this
mysql> update account set balance =1000 where name ='Wei'; Query OK, 1 row affected (0.01 sec)
2)事務B執行更新操做,更新成功spa
mysql> update account set balance =1000 where name ='Eason'; Query OK, 1 row affected (0.01 sec)
3)事務A執行插入操做,陷入阻塞~3d
mysql> insert into account values(null,'Jay',100);
這時候能夠用select * from information_schema.innodb_locks;
查看鎖狀況:日誌
4)事務B執行插入操做,插入成功,同時事務A的插入由阻塞變爲死鎖error。
mysql> insert into account values(null,'Yan',100); Query OK, 1 row affected (0.01 sec)
在分析死鎖日誌前,先作一下鎖介紹,哈哈~
主要介紹一下兼容性以及鎖模式類型的鎖:
InnoDB 實現了標準的行級鎖,包括兩種:共享鎖(簡稱 s 鎖)、排它鎖(簡稱 x 鎖)。
若是事務 T1 持有行 r 的 s 鎖,那麼另外一個事務 T2 請求 r 的鎖時,會作以下處理:
若是 T1 持有 r 的 x 鎖,那麼 T2 請求 r 的 x、s 鎖都不能被當即容許,T2 必須等待T1釋放 x 鎖才能夠,由於X鎖與任何的鎖都不兼容。
好比:事務1在表1上加了S鎖後,事務2想要更改某行記錄,須要添加IX鎖,因爲不兼容,因此須要等待S鎖釋放;若是事務1在表1上加了IS鎖,事務2添加的IX鎖與IS鎖兼容,就能夠操做,這就實現了更細粒度的加鎖。
InnoDB存儲引擎中鎖的兼容性以下表:
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
記錄鎖的事務數據(關鍵詞:lock_mode X locks rec but not gap
),記錄以下:
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 ;;
間隙鎖的事務數據(關鍵詞:gap before rec
),記錄以下:
RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38049 lock_mode X locks gap before rec Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 3; hex 576569; asc Wei;; 1: len 4; hex 80000002; asc ;;
事務數據相似於下面:
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 ;;...
鎖模式兼容矩陣(橫向是已持有鎖,縱向是正在請求的鎖):
能夠用show engine innodb status
,查看最近一次死鎖日誌哈~,執行後,死鎖日誌以下:
2020-04-11 00:35:55 0x243c *** (1) TRANSACTION: TRANSACTION 38048, ACTIVE 92 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2 MySQL thread id 53, OS thread handle 2300, query id 2362 localhost ::1 root update insert into account values(null,'Jay',100) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38048 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 3; hex 576569; asc Wei;; 1: len 4; hex 80000002; asc ;; *** (2) TRANSACTION: TRANSACTION 38049, ACTIVE 72 sec inserting, thread declared inside InnoDB 5000 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 52, OS thread handle 9276, query id 2363 localhost ::1 root update insert into account values(null,'Yan',100) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38049 lock_mode X locks gap before rec Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 3; hex 576569; asc Wei;; 1: len 4; hex 80000002; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38049 lock_mode X insert intention waiting Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** WE ROLL BACK TRANSACTION (1)
咱們如何分析以上死鎖日誌呢?
1)找到關鍵詞TRANSACTION,事務38048
2)查看正在執行的SQL
insert into account values(null,'Jay',100)
3)正在等待鎖釋放(WAITING FOR THIS LOCK TO BE GRANTED),插入意向排他鎖(lock_mode X locks gap before rec insert intention waiting),普通索引(idx_name),物理記錄(PHYSICAL RECORD),間隙區間(未知,Wei);
1)找到關鍵詞TRANSACTION,事務38049
2)查看正在執行的SQL
insert into account values(null,'Yan',100)
3)持有鎖(HOLDS THE LOCK),間隙鎖(lock_mode X locks gap before rec),普通索引(index idx_name),物理記錄(physical record),區間(未知,Wei);
4)正在等待鎖釋放(waiting for this lock to be granted),插入意向鎖(lock_mode X insert intention waiting),普通索引上(index idx_name),物理記錄(physical record),間隙區間(未知,+∞);
5)事務1回滾(we roll back transaction 1);
查看日誌可得:
這裏面,有些朋友可能有疑惑,
咱們接下來一小節詳細分析一波,一個一個問題來~
爲了方便記錄,例子用W表示Wei,J表示Jay,E表示Eason哈~
update account set balance =1000 where name ='Wei';
間隙鎖:
記錄鎖
Next-Key鎖
綜上所述,事務A執行完update更新語句,會持有鎖:
insert into account values(null,'Jay',100);
間隙鎖:
插入意向鎖(Insert Intention)
所以,事務A的update語句和insert語句執行完,它是持有了 (E,W]的 Next-Key鎖,(W,+∞)的Gap鎖,想拿到 (E,W)的插入意向排它鎖,等待的鎖跟死鎖日誌是對上的,哈哈~
update account set balance =1000 where name ='Eason';
間隙鎖:
記錄鎖
Next-Key鎖
綜上所述,事務B執行完update更新語句,會持有鎖:
insert into account values(null,'Yan',100);
間隙鎖:
插入意向鎖(Insert Intention)
因此,事務B的update語句和insert語句執行完,它是持有了 (-∞,E]的 Next-Key鎖,(E,W)的Gap鎖,想拿到 (W,+∞)的間隙鎖,即插入意向排它鎖,加鎖狀況跟死鎖日誌也是對上的~
接下來呢,讓咱們一塊兒還原死鎖真相吧~哈哈~
最後,遇到死鎖問題,咱們應該怎麼分析呢?