一【場景】
之前系統在運行過程中,老是報一個詭異的死鎖檢測異常: 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鎖,破壞了請求和保持的必要條件,預防了死鎖的發生。
不過,現在互聯網企業爲了方便分表,分庫,數據遷移等,已經越來越少的去建立表的外鍵約束,而是靠上層應用自己去保證了。