概述
插入一条记录到 mysql 数据库,如何处理可能的唯一键冲突呢?
一般做法可能是先查询一下,如果记录不存在,就可以放心插入,如果记录存在,视情况放弃插入或者是进行更新
不过先查询一下有一点点麻烦,这个步骤能省略吗?
实际上是可以的,还有不少的方法,我们来实验下,看看他们的区别
准备工作
创建一个简单的表 user 来做实验:user 表有个自增主键,在 name 字段上创建了唯一键
create table user (
id int auto_increment,
name varchar(20),
age int,
primary key (id),
unique key (name)
);
再插入一条记录
mysql> insert into user (name, age) values ('mike', 20);
Query OK, 1 row affected (0.01 sec)
此时查询一下
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | mike | 20 |
+----+------+------+
1 row in set (0.00 sec)
接下来就可以做实验了
关于自增 id 的预备知识
由于唯一键冲突,导致插入失败,自增 id 也会递增,验证如下
mysql> insert into user (name, age) values ('mike', 20);
ERROR 1062 (23000): Duplicate entry 'mike' for key 'name'
mysql> insert into user (name, age) values ('tom', 20);
Query OK, 1 row affected (0.01 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | mike | 20 |
| 3 | tom | 20 |
+----+------+------+
2 rows in set (0.00 sec)
可以看到虽然第二次插入 mike
出错,但是 id 还是递增了,有了这个预备知识,更容易理解后续的一些操作
insert ignore
在 insert
语句里增加 ignore
,就可以达到忽略唯一键冲突的效果,此时 mysql 不会报错,而是放弃执行插入。如下
mysql> insert ignore into user (name, age) values ('mike', 18);
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | mike | 20 |
| 3 | tom | 20 |
+----+------+------+
2 rows in set (0.00 sec)
虽然没有实际插入数据,但是自增 id 已经递增了。如果我们再插入一条记录,id 应该是 5。如下
mysql> insert ignore into user (name, age) values ('jack', 20);
Query OK, 1 row affected (0.01 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | mike | 20 |
| 3 | tom | 20 |
| 5 | jack | 20 |
+----+------+------+
3 rows in set (0.00 sec)
新插入的记录 id 是 5
replace into
在没有发生唯一键冲突时,replace into
的效果和 insert into
一样,如果发生了主键冲突,会删除数据库中已存在的记录并插入新的记录。如下
mysql> replace into user (name, age) values ('mike', 16);
Query OK, 2 rows affected (0.01 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 3 | tom | 20 |
| 5 | jack | 20 |
| 6 | mike | 16 |
+----+------+------+
3 rows in set (0.00 sec)
注意,replace into
语句影响了 2
行记录,且记录的 id
从 1
变成了 6
,这说明该语句先删除了 id=1
的记录,并插入了 1
条新记录
如果是以自增 id 为主键,并且其他表使用了该 id 进行关联,replace into
要慎用,可能会发生因 id 改变导致其他表关联不到正确记录的问题
insert … on duplicate key update
插入时如果发生了唯一键冲突则进行更新操作,和 replace into
的区别是该语句不会先删除后插入,而是直接修改原始记录,这样涉及到自增 id 时,不会发生 id 变化的情况。如下
mysql> insert into user (name, age) values ('mike', 14) on duplicate key update age=14;
Query OK, 2 rows affected (0.01 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 3 | tom | 20 |
| 5 | jack | 20 |
| 6 | mike | 14 |
+----+------+------+
3 rows in set (0.00 sec)
可以看到 age 被修改了,但是 id 并没有改变,这说明实际执行的是 update
操作
根据 mysql 的输出,可以看到该语句影响了 2 行记录,这是为什么呢?很明显是发生了一次未成功的 insert
后才执行的 update
,该 insert
虽然不成功,但实实在在的改变了自增 id,所以影响的行数是 2
此时插入一条记录,id 应该是 8,验证一下
mysql> insert into user (name, age) values ('ken', 20);
Query OK, 1 row affected (0.00 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 3 | tom | 20 |
| 5 | jack | 20 |
| 6 | mike | 14 |
| 8 | ken | 20 |
+----+------+------+
4 rows in set (0.00 sec)
另外,根据 mysql 文档,该语句影响行数如下
With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values.
根据文档描述,如果要插入的数据和已有的数据完全一致,影响的行数将是 0,验证一下
mysql> insert into user (name, age) values ('mike', 14) on duplicate key update age=14;
Query OK, 0 rows affected (0.01 sec)
那么此时再成功插入一条记录,id 将是 9,验证一下
mysql> insert into user (name, age) values ('ivan', 20);
Query OK, 1 row affected (0.00 sec)
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 3 | tom | 20 |
| 5 | jack | 20 |
| 6 | mike | 14 |
| 8 | ken | 20 |
| 10 | ivan | 20 |
+----+------+------+
5 rows in set (0.00 sec)
实际上,新记录的 id 是 10,我们还可以多试验几次,虽然官方文档里说插入相同的记录影响行数是 0, 但是实际上依然存在一次隐含的插入失败,导致自增 id 的递增