前段时间写游戏合服工具时出现过一个问题,源DB和目标DB角色表中主键全部都不相同,从源DB取出玩家数据再使用 replace into 写入目标DB中,结果总有几条数据插入时会导致目标DB中原有的角色数据丢失了。仔细排查之后发现时replace into使用错误造成的。在这里总结下replace into的使用方式,可以帮助有幸看到这篇文章的朋友避开replace into 使用的坑。

 

replace into 执行流程

1. 尝试向表中插入新行
2. 插入失败,因为表中存在相同的主键或唯一索引
   
    a.删除表中所有相同的主线以及唯一索引的记录
   
    b.再次尝试向表中插入新行  

与insert的区别

insert是直接插入记录,如果表中存在相同的主键或唯一索引,插入失败。   replace into也是插入记录,如果表中存在相同的主键或唯一索引,先删除相同主键或唯一索引记录,再执行插入操作。如果表中不存在相同主键或唯一索引时,和insert时相同的。  

注意

1. 使用replace into时需要对表有delete和insert的权限
2. replace into语句中所有缺失的字段都会被设置为字段的默认值
3. replace into执行记结果受影响的行数大于1行时,插入操作只有一行受影响,其他受影响的行是删除操作
4. replace..set col_name = col_name + 1时,col_name会被当做默认值,赋值最终等价于 col_name = DEFAULT(col_name) + 1     假如有如下的表:
mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `id` int(11) NOT NULL,
  `name` char(32) NOT NULL,
  `uid` int(11) NOT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_name` (`name`),
  KEY `idx_uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)



mysql> replace into test(id,name,uid,age) values(1,'aa',101,20),(2,'bb',102,21);
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0



mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  1 | aa   | 101 |   20 |
|  2 | bb   | 102 |   21 |
+----+------+-----+------+
2 rows in set (0.00 sec)

 

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

现在执行下面的操作:

mysql> replace into test(id,name,uid,age) values(2,'cc',103,22);      
Query OK, 2 rows affected (0.00 sec)
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  1 | aa   | 101 |   20 |
|  2 | cc   | 103 |   22 |
+----+------+-----+------+
2 rows in set (0.00 sec)

 

replace into语句执行之后,2行受影响,主键id=2在表中已经存在,先删除表中主键,然后再插入新行数据

 

mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  1 | aa   | 101 |   20 |
|  2 | cc   | 103 |   22 |
+----+------+-----+------+
2 rows in set (0.00 sec)
mysql> replace into test(id,name,uid,age) values(2,'aa',100,25);
Query OK, 3 rows affected (0.02 sec)
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  2 | aa   | 100 |   25 |
+----+------+-----+------+
1 row in set (0.00 sec)

 

  replace into语句执行之后,3行受影响,主键id=2在表中已经存在,先删除表中主键行,唯一索引name='aa'再表中已经存在,先删除表中索引行,然后再插入新行数据  
mysql> replace into test(id,name,uid,age) values(3,'dd',100,25);   
Query OK, 1 row affected (0.01 sec)
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  2 | aa   | 100 |   25 |
|  3 | dd   | 100 |   25 |
+----+------+-----+------+
2 rows in set (0.01 sec)

 

  replace into语句执行之后,1行受影响,表中没有相同的主键以及唯一索引相同,uid字段是普通索引,不是唯一索引,所以不会有删除操作,最终和insert语句效果一样,插入一行新数据  
mysql> replace into test set id = 3,name='dd',uid=100,age=age+1;
Query OK, 2 rows affected (0.01 sec)
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  2 | aa   | 100 |   25 |
|  3 | dd   | 100 | NULL |
+----+------+-----+------+
2 rows in set (0.00 sec)
mysql> select NULL + 1;
+----------+
| NULL + 1 |
+----------+
|     NULL |
+----------+
1 row in set (0.00 sec)
mysql> update test set age = age + 1 where id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  2 | aa   | 100 |   26 |
|  3 | dd   | 100 | NULL |
+----+------+-----+------+
2 rows in set (0.00 sec)

 

    如果使用 replace...set col_name = col_name + 1 语句的话,col_name取的是默认值,这里age的默认值是NULL,set age = age + 1 等价于 set age = NULL + 1,结果还是为NULL   使用update...set col_name = col_name + 1就不会有这个问题,这里的主要原因是 replace 会先删除重复主键或唯一索引的记录,再插入一行新数据,当删除原有数据之后 age 字段就没有值了。所以这里的 replace ...set age = age + 1,age 的最终值是NULL,我们修改下age的默认值,再执行replace...set age = age + 1看下结果  
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  2 | aa   | 100 |   26 |
|  3 | dd   | 100 | NULL |
+----+------+-----+------+
2 rows in set (0.00 sec)
mysql> alter table test alter age set default 3;   
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> replace into test set id = 2,name='aa',uid=100,age = age + 1;
Query OK, 2 rows affected (0.07 sec)
mysql> select * from test;
+----+------+-----+------+
| id | name | uid | age  |
+----+------+-----+------+
|  2 | aa   | 100 |    4 |
|  3 | dd   | 100 | NULL |
+----+------+-----+------+
2 rows in set (0.00 sec)

 

把age的默认值修改成3之后,执行replace..set age = age + 1, age最终的值不在是NULL了。  
扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄