一個數據庫死鎖的案例


昨天一個項目的生產環境出現了數據庫死鎖問題,導致死鎖的訂單號已經提交給第三方,但是由於出錯回滾到該訂單號未記錄的狀態,結果后續的單子使用的單號仍以該單號開始,這在第三方看來不是新單,而是舊單重復調用接口,就報權限錯誤;即后續所有的新單都卡在這里。這是一個很嚴重的bug,自增單號的邏輯顯然有問題,但是這里看一下死鎖問題。

查看死鎖日志后,發現涉及到死鎖的應該是兩張表,表1是關聯表,一般根據order_id和customer_id查詢

CREATE TABLE `rel_credit_customer_files` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`order_id` varchar(32) DEFAULT NULL COMMENT '',
`customer_id` varchar(32) DEFAULT NULL COMMENT '',
`file_id` varchar(32) DEFAULT NULL COMMENT '',PRIMARY KEY (`id`),
KEY `pk_order_customer` (`customer_id`,`order_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=247 DEFAULT CHARSET=utf8mb4 COMMENT='客戶圖片關聯表啊';

表2是第三方訂單信息表,order_id是本家訂單號,vx_order_id是第三方訂單號。

CREATE TABLE `biz_credit_order_info` (
  `order_id` varchar(32) NOT NULL COMMENT ' 訂單號',
  `vx_order_id` varchar(50) DEFAULT NULL COMMENT '第三方返回的訂單號',
  `withdraw_time` datetime DEFAULT NULL COMMENT '退回時間',PRIMARY KEY (`order_id`) USING BTREE,
  KEY `pk_order_id` (`order_id`) USING BTREE,
  KEY `pk_bank` (`bank`),
  KEY `pk_institution` (`institution_id`),
  KEY `bank_code` (`bank`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='征信訂單信息表';

涉及到的語句很多,查看死鎖日志后鎖定了關鍵的語句,事務1

replace into biz_credit_order_info(order_id,vx_order_id,withdraw_time) values('2020xxx20','vx001011003753200319030116352','2020-09-09 09:09:09');

delete from rel_credit_customer_files where order_id='
2020xxx20
' and customer_id=20;

insert into rel_credit_customer_files(
customer_id,
file_id,
order_id,
customer_name
) values(20,1,'2020xxx20','張三');

事務2

delete from rel_credit_customer_files where order_id='
2020xxx08
' and customer_id=8;
insert into rel_credit_customer_files(
customer_id,
file_id,
order_id,
customer_name
) values(8,2,'2020xxx08','張三');
update biz_credit_order_info
vx_order_id = 'vx001011003753187396677275648',
withdraw_time ='2020-09-09 10:25:29'
where order_id = '2020xxx08' or vx_order_id = 'vx001011003753187396677275648';

這兩個事務是由兩個不同的方法執行的(新增和更新),由於疏忽,語句的執行順序反了,這就為死鎖埋了禍根。

rel_credit_customer_files表的兩條語句只用看刪除的語句,忽略其新增語句(新增語句對死鎖形成原因沒有影響)

事務1

事務2
begin; begin;
replace into biz_credit_order_info(order_id,vx_order_id,withdraw_time) values('2020xxx20','vx001011003753200319030116352','2020-09-09 09:09:09');
(Affected rows: 2)
 
 
delete from rel_credit_customer_files where order_id='
2020xxx08
' and customer_id=8;(Affected rows: 2)
delete from rel_credit_customer_files where order_id='
2020xxx20
' and customer_id=20;(被阻塞)
 
(Deadlock found when trying to get lock; try restarting transaction)
update biz_credit_order_info
vx_order_id = 'vx001011003753187396677275648',
withdraw_time ='2020-09-09 10:25:29'
where order_id = '2020xxx08' or vx_order_id = 'vx001011003753187396677275648';
Affected rows: 0

 

問題在於sql語句寫的不嚴謹導致改語句出現了鎖表情況,第一步應該加了next_key鎖。第二步的刪除語句:本意是根據非唯一索引刪除,但是customer_id 為int值與類型不符,發生類型轉化,原本走索引變成全表掃描,結果鎖所有記錄(日志中持有136條記錄鎖)。第三步的刪除操作申請所有記錄的鎖被阻塞。第四步更新操作有 or操作,正好vx_order_id沒有索引,會進行全表掃描(explain它還是會走主鍵索引),會申請第一步中的記錄鎖而阻塞,於是發生死鎖。發生死鎖的刪除語句和更新語句雖然不是操作同一條記錄,但沒有走索引導致可能鎖所有行,而兩個事務的sql執行順序也是相反的,導致互相等待發生死鎖。以上死鎖情況如果是事務2先執行第一步同樣會死鎖。

將刪除語句的數據類型變更/更新語句的vx_order_id字段加索引/更改語句執行順序,均可以避免死鎖。死鎖日志提供的信息有限,還需要根據具體的sql執行以及分析語句的索引使用情況信息,才好找出問題。

 


免責聲明!

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



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