MySQL replace into那些隱藏的風險


MySQL中 replace into是否像預期樣:若表中有已經存在的數據,則把已經存在的數據刪除,插入新數據?

準備數據


CREATE TABLE `test_replace` ( 
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `str1` char(10) DEFAULT NULL, 
  `str2` char(10) DEFAULT NULL, 
  PRIMARY KEY (`id`), 
  UNIQUE KEY `uqx_str` (`str1`) 
) ENGINE=InnoDB;

insert into test_replace(id,str1,str2) values(2,1234,'aaabbbb'),(4,123456,'bbbbxxxx');

select * from test_replace; 
+----+--------+----------+ 
| id | str1   | str2     | 
+----+--------+----------+ 
|  2 | 1234   | aaabbbb  | 
|  4 | 123456 | bbbbxxxx | 
+----+--------+----------+ 
2 rows in set (0.00 sec)

replace into時存在主鍵衝突


replace into test_replace(id,str1,str2) values(2,'xxxx','yyy'); 
Query OK, 2 rows affected (0.00 sec)

select * from test_replace; 
+----+--------+----------+ 
| id | str1   | str2     | 
+----+--------+----------+ 
|  2 | xxxx   | yyy      | 
|  4 | 123456 | bbbbxxxx | 
+----+--------+----------+


binlog中記錄內容

replace into時存在唯一索引衝突


replace into test_replace(id,str1,str2) values(8,'xxxx','ppppp'); 
Query OK, 2 rows affected (0.01 sec)

select * from test_replace; 
+----+--------+----------+ 
| id | str1   | str2     | 
+----+--------+----------+ 
|  4 | 123456 | bbbbxxxx | 
|  8 | xxxx   | ppppp    | 
+----+--------+----------+

show create table `test_replace`\G 
*************************** 1. row *************************** 
       Table: test_replace 
Create Table: CREATE TABLE `test_replace` ( 
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `str1` char(10) DEFAULT NULL, 
  `str2` char(10) DEFAULT NULL, 
  PRIMARY KEY (`id`), 
  UNIQUE KEY `uqx_str` (`str1`) 
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
###下一次插入非衝突數據時自增主鍵為9

binlog中記錄內容

replace into時存在主鍵衝突&唯一索引衝突


replace into test_replace(id,str1,str2) values(8,'123456','主鍵和唯一索引沖 
突'); 
Query OK, 3 rows affected (0.01 sec)

####插入了這條數據後,原來的兩條數據(主鍵4,8)變成了一條(主鍵 8),數據丟失!!!
select * from test_replace; 
+----+--------+-----------------------------+ 
| id | str1   | str2                        | 
+----+--------+-----------------------------+ 
|  8 | 123456 | 主鍵和唯一索引衝突          | 
+----+--------+-----------------------------+

show create table test_replace\G 
*************************** 1. row *************************** 
       Table: test_replace 
Create Table: CREATE TABLE `test_replace` ( 
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `str1` char(10) DEFAULT NULL, 
  `str2` char(10) DEFAULT NULL, 
  PRIMARY KEY (`id`), 
  UNIQUE KEY `uqx_str` (`str1`) 
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 
1 row in set (0.00 sec)

binlog中記錄內容

存在問題

場景2:

  • replace into時存在唯一索引衝突:會把衝突數據刪掉,插入新數據,但binlog中記錄的是update格式,從庫同步update binlog不會更新該表的自增主鍵,主庫自增主鍵9,從庫自增主鍵8,若主從庫角色發生切換後,新主庫會存在主鍵衝突問題
  • replace into唯一索引衝突會導致下游大數據hive(同步binlog寫入hive中)中數據和mysql中數據不一致問題(hive基於唯一主鍵進行處理,mysql一條數據,hive中多條數據情況)

場景3:

  • replace into時存在主鍵衝突&唯一索引衝突:會把表中主鍵衝突和唯一索引衝突的數據都刪掉,再插入新數據,丟失一條數據

經驗證:mysql5.7 和mysql8.0均是上訴情況

結論

replace into在只存在主鍵衝突時會按預期的那樣;若只有唯一索引衝突時 主從切換後導致新主庫主鍵衝突錯誤、下游大數據數據不一致問題;同時存在主鍵衝突和唯一索引衝突可能會導致丟失數據。業務上不應使用replace into,應該在程式碼對唯一數據衝突作處理