一個數據庫死鎖問題

一【場景

之前系統在運行過程中,老是報一個詭異的死鎖檢測異常: Error Code: 1213

Deadlock found when trying to get lock; try restartingtransaction。最後仔細研究了一下終於解決了。場景模擬如下:

數據庫中2張表:用戶表:users,和訂單表orders。用戶表裏面有個字段total用來累計每個用戶的訂單消費總額,同時orders通過字段user_id與users表做了外鍵關聯。

表users:


CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(16) NOT NULL DEFAULT '' COMMENT '會員名',
  `total` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '消費總額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

orders:


CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `amount` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '訂單金額',
  PRIMARY KEY (`id`),
  KEY `USER_ID` (`user_id`),
  CONSTRAINT `USER_ID` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8


假設事務A與事務B按照以下序列進行,就會產生死鎖,從而引發數據庫的死鎖檢測異常,MySQL就會選擇影響行數較少的事務進行回滾:https://dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html;

序列

事務A

事務B

1

BEGIN;

INSERT INTO orders (user_id, amount) VALUES (1, 10.00);

 

2

 

BEGIN;

INSERT INTO orders (user_id, amount) VALUES (1, 25.00);

3

UPDATE users SET total=total+10.00;(發送阻塞)

 

4

 

UPDATE users SET total=total+25.00;(產生死鎖)


二【分析】

先看死鎖日誌:


根據2個事務的WAITING FOR THIS LOCK TO BE GRANTED信息可以看出事務A(D68)和事務B(D69)同時在等着給user表中的同一行加X鎖,同時D69事務已經獲取了這一行的S鎖。那麼,這兒的問題是這個共享鎖是怎麼加上的?

後來查看了Mysq的官方文檔:https://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html


就是如果存在外鍵約束,那麼會給這張表的外鍵關聯的表相應的行加上共享鎖。那麼在我們的這個場景下,就是當insert 2個訂單數據的時候,MySQL已經給user表中tom那一行加上了2把共享鎖,所以當後面再想着更新tom會員信息的時候,2個事務都在等着對方釋放各自的共享鎖,於是就產生了死鎖。

三【解決】

就目前的這個場景,當然是先更新user表,再插入orders表數據就行了。這樣就把user表的S鎖直接替換成了X鎖,破壞了請求和保持的必要條件,預防了死鎖的發生。

不過,現在互聯網企業爲了方便分表,分庫,數據遷移等,已經越來越少的去建立表的外鍵約束,而是靠上層應用自己去保證了。


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