Mysql中REPLACE INTO详解及和INSERT INTO的区别
前言
我们在进行数据库操作的时候,经常会遇到这样的场景:
- 首先判断数据是否存在;
- 如果不存在,则插入;
- 如果存在,则更新。
博主之前是是Java
来进行逻辑判断,例如:
看起来似乎也很简洁,但是博主在一次偶然的机会接触到了REPLACE INTO
。至此,这些 Java 代码全部可以省略!
接下来,我将带领大家一起学习REPLACE INTO
这个强大的 SQL 语句。
官方地址:
MySQL :: MySQL 8.0 Reference Manual :: 15.2.12 REPLACE Statement
1. REPLACE INTO 原理
REPLACE INTO
和 INSERT INTO
功能类似,不同点在于:
- 若表中的一条旧记录与一条使用
PRIMARY KEY
主键索引或使用UNIQUE
唯一索引的新记录具有相同的值,则先删除旧数据,再插入新数据。 - 否则,直接插入新数据。`
尤其需要注意的是:
除非表有一个 PRIMARY KEY
或 UNIQUE
索引,否则,使用一个 REPLACE INTO
语句没有意义。它的功能将与 INSERT INTO
一致,会直接插入数据,这将导致表中出现重复的数据!
了解了注意事项后,我们来归纳下REPLACE INTO
的执行规则
- 当表没有主键和唯一键时,
REPLACE INTO
相当于普通的INSERT
操作;binlog 记录事件为INSERT
;返回的影响行数为INSERT
的数量。 - 当表有主键没有唯一键时,
REPLACE INTO
插入记录与主键冲突时会进行DELETE+INSERT
操作;binlog 记录事件为UPDATE
;返回的影响行数为DELETE+INSERT
的数量;如果主键自增,则AUTO_INCREMENT
值不变。 - 当表有唯一键没有主键时,
REPLACE INTO
插入记录与唯一键冲突时会进行DELETE+INSERT
操作;binlog 记录事件为UPDATE
;返回的影响行数为DELETE+INSERT
的数量。 - 当表同时存在主键和唯一键时,
REPLACE INTO
插入记录与主键冲突的时候进行DELETE+INSERT
操作;binlog 记录事件为DELETE+INSERT
;返回的影响行数为DELETE+INSERT
的数量。如果主键自增,则AUTO_INCREMENT
值不变。 - 当表同时存在主键和唯一键时,
REPLACE INTO
插入记录与唯一键冲突的时候进行DELETE+INSERT
操作;binlog 记录事件为UPDATE
;返回的影响行数为DELETE+INSERT
的数量。如果主键自增,则新插入的这条记录的主键会变成最新 AUTO_INCREMENT 的值,而对应的 AUTO_INCREMENT 值会 +1。 - 当表同时存在主键和唯一键时,
REPLACE INTO
插入记录与一条记录主键和一条记录唯一键都冲突的时候进行DELETE+INSERT
操作;binlog 记录事件为DELETE+UPDATE
;返回的影响行数为DELETE*2+INSERT
的数量。
总结
下面,我用表格来总结下使用 REPLACE INTO
时可能会遇到的不同情况
- ⭐无主键无唯一索引
场景 | REPLACE INTO 是相当于 | REPLACE INTO 在 binlog 中的表现形式 | 返回的影响行数 |
无主键无唯一索引 | INSERT | INSERT | INSERT 行数 |
- ⭐只有主键
场景 | REPLACE INTO 是相当于 | REPLACE INTO 在 binlog 中的表现形式 | 返回的影响行数 |
主键冲突 | DELETE+INSERT | UPDTATE | DELETE+INSERT 行数 |
主键不冲突 | INSERT | INSERT | INSERT 行数 |
- ⭐只有唯一索引
场景 | REPLACE INTO 是相当于 | REPLACE INTO 在 binlog 中的表现形式 | 返回的影响行数 |
唯一索引冲突 | DELETE+INSERT | UPDTATE | DELETE+INSERT 行数 |
- ⭐有主键有唯一索引
场景 | REPLACE INTO 是相当于 | REPLACE INTO 在 binlog 中的表现形式 | 返回的影响行数 |
主键冲突唯一索引不冲突 | DELETE+INSERT | DELETE+INSERT | DELETE+INSERT 行数 |
主键不冲突唯一索引冲突 | DELETE+INSERT | UPDATE | DELETE+INSERT 行数 |
主键冲突唯一索引冲突 | DELETE+INSERT | DELETE+INSERT | DELETE+INSERT 行数 |
主键不冲突唯一索引不冲突 | INSERT | INSERT | INSERT 行数 |
2. REPLACE INTO 三种使用方式
value1,value2 均为模拟的表的属性名
REPLACE INTO 表名(value1, value2, ...) values(...);
REPLACE INTO 表名(value1, value2, ...) SELECT ....
REPLACE INTO 表名 SET value1 = value, ...
第一种形式与INSERT INTO
的用法类似。
第二种形式相似于 INSERT SELECT
。这种用法并不强制要求列名匹配,事实上,MYSQL 甚至不关心SELECT
返回的列名,它需要的是列的位置。
eg:REPLACE INTO tb1( name, title, mood) SELECT rname, rtitle, rmood FROM tb2;
这个例子使用REPLACE INTO
从 tb2 中将全部数据导入 tb1 中。
第三种形式相似于UPDATE SET
用法。
3. REPALCE INTO 的坑
3.1. REPLACE INTO 与数据库设置自动更新时间的坑
REPLACE INTO
在有主键或唯一索引冲突时,会执行DELETE+INSERT
操作,若字段设置了自动更新,需要手动设置时间或者字段设置默认时间,否则时间字段会插入为空。
如下sql:
drop table if exists user_test;
create table user_test(
id int primary key auto_increment comment '主键',
name varchar(30) not null comment '姓名',
update_time timestamp on update CURRENT_TIMESTAMP comment '更新时间'
) comment '测试表';
insert into user_test(name,update_time) value ('zhangsan','2000-01-01 00:00:00');
此时执行 REPLACE INTO
replace into user_test(id,name) value (1,'zhangsan');
上图可以看出执行REPLACE INTO
之后,update_time 的字段被更新为空了。
解决方案
# 方案一:建表时设置字段有默认时间
create table user_test(
id int primary key auto_increment comment '主键',
name varchar(30) not null comment '姓名',
update_time timestamp default current_timestamp on update CURRENT_TIMESTAMP comment '更新时间'
) comment '测试表';
# 方案二:replace into时手动设置时间
replace into user_test values(1,'zhangsan',now());
3.2. REPLACE INTO 匹配唯一索引更新时会导致主键自增值+1
当有主键有唯一索引且唯一索引冲突时并且主键设置成 AUTO_INCREMENT
,那么使用REPLACE INTO
会导致 AUTO_INCREMENT
值自增 1。
3.3. REPLACE INTO 同事有主键和唯一索引冲突时可能会删除多条数据
使用replace into时只能用主键更新或者唯一键更新,二选其一。若同时都冲突了,可能会导致异常
4. 总结
REPLACE INTO
存在暗坑。需谨慎使用。如果要实现上述说的需求可以使用另一种方式:on duplicate key update
!
当然,on duplicate key update
为 Mysql 特有语法并且最好在单线程下操作。不然多线程下INSERT
经常会导致锁表的情况发生,也是个小坑。在使用这些技术的时候,我们需要多加注意