mysql 处理唯一键冲突的技巧

内容纲要

概述

插入一条记录到 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 行记录,且记录的 id1 变成了 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 的递增

mysql 处理唯一键冲突的技巧

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Scroll to top
粤ICP备2020114259号 粤公网安备44030402004258