背景
Replace into操作可以算是比較常用的操作類型之一,當(dāng)我們不確定即將插入的記錄是否存在唯一性沖突時(shí),可以通過Replace into的方式讓MySQL自動(dòng)處理:當(dāng)存在沖突時(shí),會(huì)把舊記錄替換成新的記錄。
create table t1 (a int auto_increment primary key, b int, c int, unique key (b));
并發(fā)執(zhí)行SQL:replace into t1(b,c) values (2,3)
Step 1. 正常的插入邏輯
首先插入聚集索引記錄,在上例中a列為自增列,由于未顯式指定自增值,每次Insert前都會(huì)生成一個(gè)不沖突的新值。
隨后插入二級(jí)索引b,由于其是唯一索引,在檢查duplicate key時(shí),為其加上類型為LOCK_X的記錄鎖。
Step 2. 處理錯(cuò)誤
由于檢測(cè)到duplicate key,因此第一步插入的聚集索引記錄需要被回滾掉(row_undo_ins)。
Step 3. 轉(zhuǎn)換操作
從InnoDB層失敗返回到Server層后,收到duplicate key錯(cuò)誤,首先檢索唯一鍵沖突的索引,并對(duì)沖突的索引記錄(及聚集索引記錄)加鎖。
隨后確認(rèn)轉(zhuǎn)換模式以解決沖突:
-如果發(fā)生uk沖突的索引是最后一個(gè)唯一索引、沒有外鍵引用、且不存在delete trigger時(shí),使用UPDATE ROW的方式來解決沖突;
-否則,使用DELETE ROW + INSERT ROW的方式解決沖突。
Step 4. 更新記錄
對(duì)于聚集索引,由于PK列發(fā)生變化,采用delete + insert 聚集索引記錄的方式更新。
對(duì)于二級(jí)uk索引,同樣采用標(biāo)記刪除 + 插入的方式。
我們知道,在嘗試插入一條記錄時(shí),如果插入位置的下一條記錄上存在記錄鎖,那么在插入時(shí),當(dāng)前session需要對(duì)其加插入意向鎖,具體類型為LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION。這也是導(dǎo)致死鎖的關(guān)鍵點(diǎn)之一。
tips:
默認(rèn)情況下,參數(shù)innodb_autoinc_lock_mode的值為1,因此只在分配自增列時(shí)互斥(如果我們將其設(shè)為0的話,就會(huì)產(chǎn)生AUTO_INC類型的表級(jí)鎖)。當(dāng)分配完自增列值后,我們并不知道并發(fā)的replace into的順序。