記錄那些詭異的數據庫死鎖

場景一:多條事務引起(不易排查, 事務隔離級別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的鎖模式的簡單介紹文章:https://yq.aliyun.com/articles/696114

官方關於innodb的鎖的模式介紹文章:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

該死鎖的解決方案

將for update替換爲樂觀鎖,也就是在where條件後面加上一些期望值;但不符合當前業務;

將此業務的事務隔離級別設置爲Serializable,串行執行;

使用分佈式鎖,和上面串行思想一樣;

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

未完待續…

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