MySQL死鎖案例分:先delete,再insert,導致死鎖


一、死鎖案例

MySQL版本:Percona MySQL Server 5.7.19
隔離級別:可重復讀(RR)
業務邏輯:並發下按某個索引字段先delete記錄,再insert記錄

比如:

  1.  
    begin;
  2.  
    delete from tb where order_id = xxx;
  3.  
    insert into tb(order_id) values(xxx);
  4.  
    commit;

二、MySQL鎖基本概念

S:共享鎖(行級鎖)
X:排他鎖(行級鎖)
IS:意向共享鎖(表級鎖)
IX:意向排他鎖(表級鎖)

以上4種鎖的兼容性見下表:


鎖模式兼容性表

  • gap鎖與gap鎖之間不沖突
  • rec insert intention(插入意向鎖)與gap鎖沖突。

三、模擬復現死鎖

打開參數,從innodb status獲取更多的鎖信息。
set GLOBAL innodb_status_output_locks=ON;

表結構:

  1.  
    CREATE TABLE `tb` (
  2.  
    `order_id` int(11) DEFAULT NULL,
  3.  
    KEY `idx_order_id` (`order_id`)
  4.  
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8

表中數據:

  1.  
    mysql> select * from tb;
  2.  
    + ----------+
  3.  
    | order_id |
  4.  
    + ----------+
  5.  
    | 10 |
  6.  
    | 20 |
  7.  
    + ----------+
  8.  
    2 rows in set (0.00 sec)

事務執行步驟:

session1 session2
begin  
  begin
delete from tb where order_id=15;  
  delete from tb where order_id=15;
  insert into tb select 15;(等待鎖)
insert into tb select 15;(死鎖)  
  1. 當session1執行delete from tb where order_id=15;,由於條件order_id=15的記錄不存在,session1 獲得2個鎖結構,分別是意向排他鎖IX(表級鎖)、gap鎖(行級鎖),如下:
  1.  
    ---TRANSACTION 1055191443, ACTIVE 20 sec
  2.  
    2 lock struct(s), heap size 1136, 1 row lock(s)
  3.  
    MySQL thread id 315642, OS thread handle 139960342456064, query id 150462030 localhost root
  4.  
    TABLE LOCK table `db`.`tb` trx id 1055191443 lock mode IX
  5.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191443 lock_mode X locks gap before rec
  1. 當session2執行delete from tb where order_id=15;,同樣由於order_id=15的記錄不存在,session2 也獲得2個鎖結構,分別是意向排他鎖IX(表級鎖)、gap鎖(行級鎖),如下:
  1.  
    ---TRANSACTION 1055191444, ACTIVE 3 sec
  2.  
    2 lock struct(s), heap size 1136, 1 row lock(s)
  3.  
    MySQL thread id 315336, OS thread handle 139960562685696, query id 150462412 localhost root
  4.  
    TABLE LOCK table `db`.`tb` trx id 1055191444 lock mode IX
  5.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec
  1. 當session2執行insert into tb select 15;, session2 已經獲取到IX鎖,gap鎖,等待 rec insert intention(插入意向鎖)
  1.  
    ---TRANSACTION 1055191444, ACTIVE 68 sec inserting
  2.  
    mysql tables in use 1, locked 1
  3.  
    LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
  4.  
    MySQL thread id 315336, OS thread handle 139960562685696, query id 150462778 localhost root executing
  5.  
    insert into tb select 15
  6.  
    ------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
  7.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec insert intention waiting
  8.  
    ------------------
  9.  
    TABLE LOCK table `db`.`tb` trx id 1055191444 lock mode IX
  10.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec
  11.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec insert intention waiting
  1. 當session1執行insert into tb select 15;,session1 已獲取到IX鎖,gap鎖, 等待rec insert intention(插入意向鎖), session1, session2 都在等待插入意向鎖, 插入意向鎖與gap鎖沖突,雙方都沒有釋放gap鎖,又都在等待插入意向鎖,死鎖發生。
  1.  
    LATEST DETECTED DEADLOCK
  2.  
    ------------------------
  3.  
    2018-11-03 17:15:11 0x7f4b0e7ea700
  4.  
    *** (1) TRANSACTION:
  5.  
    TRANSACTION 1055191444, ACTIVE 135 sec inserting
  6.  
    mysql tables in use 1, locked 1
  7.  
    LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
  8.  
    MySQL thread id 315336, OS thread handle 139960562685696, query id 150462778 localhost root executing
  9.  
    insert into tb select 15
  10.  
    *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
  11.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec insert intention waiting
  12.  
    *** (2) TRANSACTION:
  13.  
    TRANSACTION 1055191443, ACTIVE 201 sec inserting, thread declared inside InnoDB 5000
  14.  
    mysql tables in use 1, locked 1
  15.  
    3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
  16.  
    MySQL thread id 315642, OS thread handle 139960342456064, query id 150463172 localhost root executing
  17.  
    insert into tb select 15
  18.  
    *** (2) HOLDS THE LOCK(S):
  19.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191443 lock_mode X locks gap before rec
  20.  
    *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
  21.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191443 lock_mode X locks gap before rec insert intention waiting
  22.  
    *** WE ROLL BACK TRANSACTION (2)

四、案例擴展

以上死鎖案例,業務代碼邏輯是多線程並發下,有可能多個線程會執行相同order_id的job,比如兩個線程執行的order_id 都是15。
另外一種情況,多個線程間,不會執行到相同order_id的情況,也可能發生死鎖。比如一個線程order_id=15,另外一個線程order_id=16,如下所示:

事務執行步驟:

session1 session2
begin  
  begin
delete from tb where order_id=15;  
  delete from tb where order_id=16;
  insert into tb select 16;(等待鎖)
insert into tb select 15;(死鎖)  

鎖情況與上述相同,不再贅述,死鎖信息如下:

  1.  
    LATEST DETECTED DEADLOCK
  2.  
    ------------------------
  3.  
    2018-11-03 17:28:30 0x7f4b0e667700
  4.  
    *** (1) TRANSACTION:
  5.  
    TRANSACTION 1055191450, ACTIVE 18 sec inserting
  6.  
    mysql tables in use 1, locked 1
  7.  
    LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
  8.  
    MySQL thread id 316221, OS thread handle 139960338228992, query id 150467652 localhost root executing
  9.  
    insert into tb select 16
  10.  
    *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
  11.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191450 lock_mode X locks gap before rec insert intention waiting
  12.  
    *** (2) TRANSACTION:
  13.  
    TRANSACTION 1055191449, ACTIVE 28 sec inserting, thread declared inside InnoDB 5000
  14.  
    mysql tables in use 1, locked 1
  15.  
    3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
  16.  
    MySQL thread id 316222, OS thread handle 139960340870912, query id 150467681 localhost root executing
  17.  
    insert into tb select 15
  18.  
    *** (2) HOLDS THE LOCK(S):
  19.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191449 lock_mode X locks gap before rec
  20.  
    *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
  21.  
    RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191449 lock_mode X locks gap before rec insert intention waiting
  22.  
    *** WE ROLL BACK TRANSACTION (2)
  23.  
     

五、解決方案

    1. 修改隔離級別為提交讀(RC)
    2. 修改業務代碼邏輯,刪除記錄之前,先select,確認該記錄存在,再執行delete刪除該記錄。


免責聲明!

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



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