1. 程式人生 > 程式設計 >記錄那些詭異的資料庫死鎖

記錄那些詭異的資料庫死鎖

場景一:多條事務引起(不易排查,事務隔離級別RR和RC都會出現)

場景描述

有一個每日交易任務,當交易額度滿足一定數量時,就可以增加一次任務完成記錄(user_no和trade_date組成唯一索引);實現流程如下:

1.當有交易來的時候,先查詢當天該使用者是否已經發生過交易了;
模擬sql: select xx1,xx2 from trade where user_no = vv3 and trade_date = vv4

2.如果當前交易不存在則進行插入或更新(主要插入時可能會有併發產生),如果存在就直接更新;
模擬sql:insert into trade(xx1,xx2,xx3,xx4) values(vv1,vv2,vv3,vv4);執行需要捕捉duplicate異常
插入異常後執行更新;

3.更新操作使用悲觀鎖(for update)進行鎖定後再做修改;
鎖定是為了獲取最新交易額,來判定是否需要新增完成次數,否則樂觀鎖即可;也就不會出現死鎖;
模擬鎖定sql: select xx1,xx2 from trade where user_no = vv3 and trade_date = vv4 for update;
模擬修改sql: update trade set xx1=vv1,xx2=vv2 where user_no = vv3 and trade_date = vv4;

建立模型實操模擬

  • 建立模擬表如下:
create table lock_test1(
    id
int unsigned primary key auto_increment comment '自增主鍵',biz_id varchar(10) not null comment '業務編號',unique KEY uniq_biz_id (biz_id) ) engine=InnoDB DEFAULT CHARSET=utf8mb4 comment='鎖定場景1演示表'; 複製程式碼
  • 建立三個session連線並開啟事務(以下按時間線執行)

第一個事務開啟和執行如下:

-- 事務一
start transaction;
select id,biz_id from lock_test1 where
biz_id = 'test1'; -- 此時發現沒有任何資料,緊接著進行插入: insert into lock_test1(biz_id) values ('test1'); -- 於此同時事務二三也執行相同步驟 複製程式碼

第二個事務開啟和執行如下:

-- 事務二
start transaction;
select id,biz_id from lock_test1 where biz_id = 'test1';
-- 此時發現沒有任何資料,緊接著進行插入(稍晚於事務一):
insert into lock_test1(biz_id) values ('test1');

-- 於此同時事務一三也執行相同步驟
複製程式碼

第三個事務開啟和執行如下:

-- 事務三
start transaction;
select id,biz_id from lock_test1 where biz_id = 'test1';
-- 此時發現沒有任何資料,緊接著進行插入(稍晚於事務一):
insert into lock_test1(biz_id) values ('test1');
-- 於此同時事務一二也執行相同步驟
複製程式碼

此時第一個事務commit

-- 事務一
commit;
-- 此時事務一已經處理完畢
複製程式碼

事務二和事務三都會丟擲(duplicate的異常),此時進入update流程;

-- 事務二
select id,biz_id from lock_test1 where biz_id = 'test1' for update;

-- 事務三
select id,biz_id from lock_test1 where biz_id = 'test1' for update;
複製程式碼

然後就出現Deadlock found when trying to get lock;try restarting transaction的資訊;
也就是說出現了死鎖

死鎖原因分析

根據時間線分析: 事務一首先拿到寫鎖(X),此時事務二和三過來試圖進行插入操作,首先獲取寫意向鎖(IX);
當事務一插入後commit後,事務二三都出現duplicate的異常,此時寫意向鎖(X)轉換成行級讀共享鎖(S);
此時事務二和事務三都執行for update想要獲取行級排他鎖(X),但是鎖X的獲取條件為所有關於該行的
其他session的任何鎖全部釋放;所以就出現了事務二想要獲取X鎖,需要等待事務三的共享鎖(S)釋放;
事務三想要獲取X鎖也需要等待事務二的共享鎖(S)釋放,所以就導致了此次的死鎖;

所以究其原因就是在這種情況下只有3個或以上的事務同時處理該邏輯才會出現死鎖。

關於innodb的鎖模式的簡單介紹文章:yq.aliyun.com/articles/69…
官方關於innodb的鎖的模式介紹文章:dev.mysql.com/doc/refman/…

該死鎖的解決方案

將for update替換為樂觀鎖,也就是在where條件後面加上一些期望值;但不符合當前業務;
將此業務的事務隔離級別設定為Serializable,序列執行;
使用分散式鎖,和上面序列思想一樣;

場景二:使用for update鎖定不存在記錄導致死鎖(隔離級別RR)

未完待續...