MySQL Insert 死鎖


insert 死鎖

insert 上鎖步驟

insert語句上鎖的大致過程如下:

1、在行所在的間隙上申請“意向插入鎖”。2、申請所要插入行的“排他鎖”。3、如果在第二步的時候引發了唯一鍵沖突,那么陷入沖突的事務,要把上鎖的過程分兩步,先申請行的“共享鎖”,然后再申請“排他鎖”; 如果有多個事物陷入沖突,那么他們一定都會申請到“共享鎖”,然后在申請排他鎖的相互等待(死鎖),這個時候 MySQL 會選擇犧牲掉其中一些事務,讓其中的一個完成。


復現 insert 死鎖

按理論一步一步的復現 insert 死鎖的場景。

會話一 會話二 會話三
create table t(x int primary key);    
start transaction;    
insert into t(x) values(1024);    
  insert into t(x) values(1024); insert into t(x) values(1024);
rollback commit successful ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

show engine innodb status 輸出中關於死鎖的信息如下。

------------------------ LATEST DETECTED DEADLOCK ------------------------ 2019-09-03 19:29:30 0x7f9ab00b4700 *** (1) TRANSACTION: TRANSACTION 2759830, ACTIVE 9 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 13, OS thread handle 140302402717440, query id 1221 127.0.0.1 root update insert into t(x) values(1024) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 16 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 2759830 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;; *** (2) TRANSACTION: TRANSACTION 2759831, ACTIVE 6 sec inserting mysql tables in use 1, locked 1 4 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 14, OS thread handle 140302355220224, query id 1222 127.0.0.1 root update insert into t(x) values(1024) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 16 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 2759831 lock mode S Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 16 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 2759831 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 (2) 

MySQL 為什么要這么做

大前提是這樣的,兩個事務插入同一條記錄,必定有一個要失敗。死鎖也是必定要有一個失敗,那么何不把並發插入也合並到死鎖的處理流程中去呢?想到就要做到,那把上行鎖的過程拆一下, 先上“S 共享鎖” 再上“X 排他鎖”,由於“S”可以共享所以兩個都能完成這一步,第二小要“X”所以兩個事務就相互等待(死鎖)。


解決方案

如果你在業務上還真遇到了上面的場景,然而 MySQL 死鎖了。你又不想讓它死鎖,那怎么辦呢?MySQL 不是把上鎖的步驟拆開了嗎?那我們把它合起來。

select ... for update 直接對行上排他鎖,通過它我們可以把分開的兩步合起來,on duplicate key update 當遇到沖突時會直接更新,不會再報錯;結合這兩大神器代碼可以改成如下的形式。

會話一 會話二 會話三
create table t(x int primary key);    
start transaction;    
select x from t for update; insert into t(x) values(1024) on duplicate key update x = 1024;    
  select x from t for update; insert into t(x) values(1024) on duplicate key update x = 1024; select x from t for update; insert into t(x) values(1024) on duplicate key update x = 1024;
rollback commit successful commit successful

副作用

由於 select for update 是排他鎖,所以並發性上會有些問題,建議與read-committed一起使用。


彩蛋

根據分析可以知道問題發生在行鎖上,把隔離級別調整成 READ-COMMITTED 也只會影響 gap 鎖,我以為這個隔離級別下不會再有插入意向鎖(一種特別的 gap)了,沒想到還有。

mysql> show variables like '%iso%'; +-----------------------+----------------+ | Variable_name | Value | +-----------------------+----------------+ | transaction_isolation | READ-COMMITTED | +-----------------------+----------------+ 1 row in set (0.00 sec) 

執行同樣的操作。

會話一 會話二 會話三
create table t(x int primary key);    
start transaction;    
insert into t(x) values(1024);    
  insert into t(x) values(1024); insert into t(x) values(1024);
rollback commit successful ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

------------------------ LATEST DETECTED DEADLOCK ------------------------ 2020-04-11 13:57:15 0x7ff1bccf1700 *** (1) TRANSACTION: TRANSACTION 6946, ACTIVE 12 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 9, OS thread handle 140676621076224, query id 237 127.0.0.1 root update insert into t(x) values(1024) *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 5 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 6946 lock mode S Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 5 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 6946 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;; *** (2) TRANSACTION: TRANSACTION 6947, ACTIVE 9 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 13, OS thread handle 140676151592704, query id 258 127.0.0.1 root update insert into t(x) values(1024) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 5 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 6947 lock mode S Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 5 page no 4 n bits 72 index PRIMARY of table `tempdb`.`t` trx id 6947 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 (2) 

READ-COMMITTED 隔離級別下已經沒有 gap 鎖了,然而官方文檔上說 insert intention 是 gap 的一種,所以 insert intention 應該不存在才對呀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM