MySQL REPLACE INTO 的使用
前段时间写游戏合服工具时出现过一个问题,源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)
现在执行下面的操作:
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了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】博客园2025新款「AI繁忙」系列T恤上架,前往周边小店选购
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 解锁.NET 9性能优化黑科技:从内存管理到Web性能的最全指南
· 通过一个DEMO理解MCP(模型上下文协议)的生命周期
· MySQL下200GB大表备份,利用传输表空间解决停服发版表备份问题
· 记一次 .NET某固高运动卡测试 卡慢分析
· 微服务架构学习与思考:微服务拆分的原则
· AI浏览器自动化实战
· Chat to MySQL 最佳实践:MCP Server 服务调用
· 解锁.NET 9性能优化黑科技:从内存管理到Web性能的最全指南
· .NET周刊【3月第5期 2025-03-30】
· 重生之我是操作系统(八)----文件管理(上)