INSERT ON DUPLICATE UPDATE與死鎖
在MySQL中提供兩種插入更新的方式:REPLACE INTO和INSERT ON DUPLICATE UPDATE,簡化了「存在則更新,不存在則插入」的實現邏輯,但這兩種方式在MySQL內部都被拆分爲多個操做步驟且引入GAP鎖來保證數據完整性,所以在高併發狀況下極易產生死鎖。
##==================================================##
在MySQL中INSERT ON DUPLICATE UPDATE的加鎖流程:
1. Innodb存儲引擎嘗試INSER INTO操做
2. 若是插入成功,則忽略DUPLICATE UPDATE部分並返回
3. 若是插入失敗,則代表有相同記錄存在,對該記錄加共享鎖(S)
4. 執行DUPLICATE UPDATE語句,對記錄加X鎖,而後更新記錄。併發
##==================================================##
對於INSERT ON DUPLICATE UPDATE操做,當兩個會話S1和S2使用INSERT ON DUPLICATE UPDATE語句操做相同數據且表中存在相同鍵值記錄時,觸發死鎖場景爲:
1. 因爲表中已存在重複鍵值的記錄,致使會話前後嘗試INSER失敗
2. 會話S1進入步驟3嘗試獲取記錄的S鎖,該記錄未被其餘會話加鎖,獲取S鎖成功。
3. 會話S2進入步驟3嘗試獲取記錄的S鎖,該記錄上被加持S鎖,但因爲S鎖與S鎖兼容,獲取S鎖成功
4. 會話S1進入步驟4嘗試獲取記錄的X鎖,因爲會話S2對該記錄持有S鎖,S鎖與X鎖不兼容,獲取X鎖失敗,會話S1被阻塞
5. 會話S2進入步驟4嘗試獲取記錄的X鎖,因爲會話S1對該記錄持有S鎖,S鎖與X鎖不兼容,獲取X鎖失敗,會話S2被阻塞
6. 會話S2被阻塞後進入死鎖檢查環節,發現阻塞S1->S2和S2->S1造成死鎖環路,觸發死鎖機制強制回滾S1或S2事務。高併發
##==================================================##
在MySQL默認隔離級別REPEATABLE READ下,爲避免出現"幻讀"發生,防止其餘會話插入相同鍵值的記錄。
對於普通INSERT操做加鎖以下:
1. 對於非惟一索引,須要對新記錄加排他鎖(X),另外對新記錄和新記錄的相鄰記錄的區間加gap鎖。
2. 對於惟一索引,僅須要對新記錄加排他鎖(X),惟一索引特性保證其餘會話沒法插入相同鍵值。測試
對於INSERT ON DUPLICATE UPDATE操做,當表中未存在重複鍵值記錄時,加鎖特色以下:
1. 對於惟一索引和非惟一索引,都須要對新記錄加排他鎖(X),另外對新記錄和新記錄的相鄰記錄的區間加gap鎖。索引
##==================================================##
準備測試數據:
CREATE TABLE `tb2002` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c1` varchar(20) DEFAULT NULL,
`c2` varchar(20) DEFAULT NULL,
`c3` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_c1` (`c1`,`c2`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;事務
insert into tb2002(c1,c2) values('a','1');
insert into tb2002(c1,c2) values('f','1');數據
開始測試:
會話1執行:
insert into tb2002(c1,c2) values('d','2') ON DUPLICATE KEY UPDATE c3=c3+1;
執行成功異常
會話2執行:
insert into tb2002(c1,c2) values('c','1') ON DUPLICATE KEY UPDATE c3=c3+1;
執行被阻塞,等待X+GAP鎖,GAP鎖的行記錄目標是('f','1')db
會話1執行:
insert into tb2002(c1,c2) values('d','1') ON DUPLICATE KEY UPDATE c3=c3+1;
觸發死鎖,會話2被回滾,會話1執行成功兼容
##==================================================##
經過上面兩個死鎖案例,咱們強烈建議在生產環境中儘可能避免使用REPLACE INTO和INSERT INTO ON DUPLICATE UPDATE語句,改用普通INSERT操做,並對INSER操做部分代碼加入異常加查,當INSERT失敗時改成UPDATE操做。阻塞