Mysql批量插入数据时如何解决重复问题?

版权声明:原创不易,未经作者允许请勿随意转载!因个人能力和精力有限,难免有疏漏和不足之处,欢迎指正,谢谢~ https://blog.csdn.net/lijing742180/article/details/90243470

本文内容仅在 MariaDB-10.2.15 版本下验证,其它环境下可能略有差异。

当数据库中存量数据较多时,或者是在批量插入操作时,很容易出现插入重复数据的问题。

一、三种方法

在 mysql 中,当存在主键冲突或唯一键冲突的情况下,根据插入策略不同,一般有以下三种避免方法:

  • insert ignore into:若没有则插入,若存在则忽略
  • replace into:若没有则正常插入,若存在则先删除后插入
  • insert into ... on duplicate key update:若没有则正常插入,若存在则更新

注意,使用以上方法的前提是表中有一个 PRIMARY KEYUNIQUE 约束/索引,否则,使用以上三个语句没有特殊意义,与使用单纯的 INSERT INTO 效果相同。

在具体介绍三种方法的使用方式之前,均使用如下语句初始化表数据:

"-- 创建表 ljtest --"
CREATE TABLE IF NOT EXISTS ljtest (
id INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,  -- 自增主键 id
emp_no INT(11) NOT NULL,
title VARCHAR(50) NOT NULL,
from_date DATE NOT NULL,
to_date DATE DEFAULT NULL)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

"-- 插入三条数据 --"
INSERT INTO ljtest VALUES 
('1', '10001', 'Senior Engineer', '1986-06-26', '9999-01-01'),
('2', '10002', 'Staff', '1996-08-03', '9999-01-01'),
('3', '10003', 'Senior Engineer', '1995-12-03', '9999-01-01'),

"-- 查看表数据 --"
select * from ljtest;
+----+--------+-----------------+------------+------------+
| id | emp_no | title           | from_date  | to_date    |
+----+--------+-----------------+------------+------------+
|  1 |  10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
|  2 |  10002 | Staff           | 1996-08-03 | 9999-01-01 |
|  3 |  10003 | Senior Engineer | 1995-12-03 | 9999-01-01 |
+----+--------+-----------------+------------+------------+
3 rows in set (0.00 sec)

"-- 查看此时初始状态下的表结构 --"
show create table ljtest\G
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意: 初始状态下 AUTO_INCREMENT=4

一、insert ignore into

1、作用

  • insert ignore 会根据主键或者唯一键判断,忽略数据库中已经存在的数据
  • 若数据库没有该条数据,就插入为新的数据,跟普通的 insert into 一样
  • 若数据库有该条数据,就忽略这条插入语句,不执行插入操作。

2、主键冲突情况

1. 同时向表中插入两条包含主键的数据:id = 2(表中已有),id = 4(表中没有)

> insert ignore into ljtest(id, emp_no) values (2, 20002), (4, 40004);       
Query OK, 1 row affected, 3 warnings (0.00 sec)
Records: 2  Duplicates: 1  Warnings: 3

注意:

  • insert ignore into 与普通 insert into 的使用方法几乎完全一样
    • 插入时要么指定插入的具体列,要么不指定列名插入全部列值,要么对字段加有默认值
    • 否则会报错:
      • ERROR 1136 (21S01): Column count doesn't match value count at row 1
      • ERROR 1364 (HY000): Field 'title' doesn't have a default value
  • 注意插入后返回的信息:
    • 1 row affected:插入成功一条数据
    • 3 warnings:有三条警告信息
    • Duplicates: 1:有一条重复数据

2. 查看 warning 警告信息

> show warnings;
+---------+------+------------------------------------------------+
| Level   | Code | Message                                        |
+---------+------+------------------------------------------------+
| Warning | 1364 | Field 'title' doesn't have a default value     |
| Warning | 1364 | Field 'from_date' doesn't have a default value |
| Warning | 1062 | Duplicate entry '2' for key 'PRIMARY'          |
+---------+------+------------------------------------------------+
3 rows in set (0.00 sec)

其中的关键信息就是告诉我们主键为 2 的数据重复了。

3. 查看插入后的表数据

> select * from ljtest;
+----+--------+-----------------+------------+------------+
| id | emp_no | title           | from_date  | to_date    |
+----+--------+-----------------+------------+------------+
|  1 |  10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
|  2 |  10002 | Staff           | 1996-08-03 | 9999-01-01 |
|  3 |  10003 | Senior Engineer | 1995-12-03 | 9999-01-01 |
|  4 |  40004 |                 | 0000-00-00 | NULL       |
+----+--------+-----------------+------------+------------+
4 rows in set (0.00 sec)

其中,id = 2 的记录还是之前的数据,id = 4 的数据是新插入的。

即,insert ignore into 会忽略已存在数据(按主键或唯一键判断),只插入新数据。

4. 查看插入后的表结构

> show create table ljtest\G
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意: 插入后 AUTO_INCREMENT=5 (从初始 4 增加为 5),这是正常的。

3、唯一键冲突情况

先重建表数据,对 emp_no 字段增加 unique 约束,并初始化同样的三条数据。

1. 向表中插入一条包含唯一键冲突的数据:emp_no = 10003(表中已有)

insert ignore ljtest(emp_no, title, from_date) values (10003, 'ignore test 3', '0000-00-00');

Query OK, 0 rows affected, 1 warning (0.00 sec)

2. 查看插入后的表数据

跟原数据一样,未发生任何变化。

3. 查看插入后的表结构

> show create table ljtest\G
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `emp_no` (`emp_no`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意: 虽然未插入成功,但 AUTO_INCREMENT=5 (从初始 4 增加为 5),再次新增数据时,id 将会从 5 开始。

二、insert into ... on duplicate key update

1、作用

  • insert into 语句末尾指定 on duplicate key update,会根据主键或者唯一键判断:
    • 若数据库有该条数据,则直接更新原数据,相当于 update
    • 若数据库没有该条数据,则插入为新的数据,跟普通的 insert into 一样

2、主键冲突情况

1. 同时向表中插入两条包含主键的数据:id = 2(表中已有),id = 4(表中没有)

> insert into ljtest(id, emp_no, title, from_date) values (2, 20002, 'insert test 2', '0000-00-00'), (4, 40004, 'insert test 4', '0000-00-00') on duplicate key update emp_no=values(emp_no), title=values(title);           

Query OK, 3 rows affected (0.00 sec)
Records: 2  Duplicates: 1  Warnings: 0

其中:

  • 与普通 insert into 的使用语法差别不大,只是语句结尾有所区别
  • emp_no=values(emp_no), title=values(title)
    • 表示当存在主键或唯一键冲突时,使用插入语句中对应的 emp_notitle 值替换原数据
    • 同时,还可以通过运算表达式来指定字段值,如:emp_no=emp_no+1, title=title+"test"
  • 因为这里未指定 update from_date 字段,所以当 id 重复时,不会更新 from_date 字段。
  • 3 row affected
    • 插入一条数据,受影响的行为 1;
    • 表中原有记录被更新,受影响的行计为 2。

2. 查看插入后的表数据

> select * from ljtest;
+----+--------+-----------------+------------+------------+
| id | emp_no | title           | from_date  | to_date    |
+----+--------+-----------------+------------+------------+
|  1 |  10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
|  2 |  20002 | insert test 2  | 1996-08-03 | 9999-01-01 |
|  3 |  10003 | Senior Engineer | 1995-12-03 | 9999-01-01 |
|  4 |  40004 | insert test 4  | 0000-00-00 | NULL       |
+----+--------+-----------------+------------+------------+
4 rows in set (0.00 sec)

其中,id = 2 的记录中 emp_no 和 title 字段被替换成新的数据,其它字段还是原来的值。
id = 4 的数据是新插入的。

3. 查看插入后的表结构

> show create table ljtest\G     
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意: 插入后 AUTO_INCREMENT=5 (从初始 4 增加为 5),这是正常的。

3、唯一键冲突情况

先重建表数据,对 emp_no 字段增加 unique 约束,并初始化同样的三条数据。

1. 向表中插入一条包含唯一键冲突的数据:emp_no = 10003(表中已有)

insert into ljtest(emp_no, title, from_date) values (10003, 'insert test 3', '0000-00-00') on duplicate key update title=values(title);

Query OK, 2 rows affected (0.00 sec)

2. 查看插入后的表数据

+----+--------+-----------------+------------+------------+
| id | emp_no | title           | from_date  | to_date    |
+----+--------+-----------------+------------+------------+
|  1 |  10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
|  2 |  10002 | Staff           | 1996-08-03 | 9999-01-01 |
|  3 |  10003 | insert test 3   | 1995-12-03 | 9999-01-01 |
+----+--------+-----------------+------------+------------+
3 rows in set (0.00 sec)

其中,emp_no = 10003 记录的 title 被更新了,但主键 id 不变,即更新了原记录的指定列数据。

3. 查看插入后的表结构

> show create table ljtest\G
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意: 插入后 AUTO_INCREMENT=5 (从初始 4 增加为 5),再次新增数据时,id 将会从 5 开始。

三、replace into

1、作用

  • replace into 会根据主键或者唯一键判断:
    • 若表中已存在该数据,则先删除此行数据,然后插入新的数据,相当于 delete + insert
      • 可能会丢失数据、主从服务器的 AUTO_INCREMENT 不一致。
    • 若表中不存在该数据,则直接插入新数据,跟普通的 insert into 一样

2、主键冲突情况

1. 同时向表中插入两条包含主键的数据:id = 2(表中已有),id = 4(表中没有)

> replace into ljtest(id, emp_no, title, from_date) values (2, 20002, 'replace test 2', '0000-00-00'), (4, 40004, 'replace test 4', '0000-00-00'); 

Query OK, 3 rows affected (0.00 sec)
Records: 2  Duplicates: 1  Warnings: 0

注意:

  • replace into 与普通 insert into 的使用语法几乎一样
  • 注意插入后返回的信息:
    • 3 row affected:删除一条数据,插入两条数据
    • Duplicates: 1:有一条重复数据

2. 查看插入后的表数据

> select * from ljtest;
+----+--------+-----------------+------------+------------+
| id | emp_no | title           | from_date  | to_date    |
+----+--------+-----------------+------------+------------+
|  1 |  10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
|  2 |  20002 | replace test 2  | 0000-00-00 | NULL       |
|  3 |  10003 | Senior Engineer | 1995-12-03 | 9999-01-01 |
|  4 |  40004 | replace test 4  | 0000-00-00 | NULL       |
+----+--------+-----------------+------------+------------+
4 rows in set (0.00 sec)

其中,id = 2 的记录被替换成新的数据、且 to_date 字段原数据丢失,id = 4 的数据是新插入的。

4. 查看插入后的表结构

> show create table ljtest\G     
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意:插入后 AUTO_INCREMENT=6 (从初始 4 增加为 5),这是正常的。

3、唯一键冲突情况

先重建表数据,对 emp_no 字段增加 unique 约束,并初始化同样的三条数据。

1. 向表中插入一条包含唯一键冲突的数据:emp_no = 10003(表中已有)

replace into ljtest(emp_no, title, from_date) values (10003, 'replace test 3', '0000-00-00');

Query OK, 2 rows affected (0.00 sec)

2. 查看插入后的表数据

+----+--------+-----------------+------------+------------+
| id | emp_no | title           | from_date  | to_date    |
+----+--------+-----------------+------------+------------+
|  1 |  10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
|  2 |  10002 | Staff           | 1996-08-03 | 9999-01-01 |
|  4 |  10003 | replace test 3  | 0000-00-00 | NULL       |
+----+--------+-----------------+------------+------------+
3 rows in set (0.00 sec)

其中,emp_no = 10003 记录的 title 被更新了,同时主键 id 也变成 4 ,即执行了 delete + insert 操作。

3. 查看插入后的表结构

> show create table ljtest\G
*************************** 1. row ***************************
       Table: ljtest
Create Table: CREATE TABLE `ljtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意: 插入后 AUTO_INCREMENT=5 (从初始 4 增加为 5),再次新增数据时,id 将会从 5 开始。

总结:

  • 在主键冲突情况下,三种方法都可以使用
  • 在唯一键冲突情况下,且有自增主键时:
    • 三种方法都会出现 AUTO_INCREMENT 不连续问题,且这种不连续不会同步更新到 slave 的 AUTO_INCREMENT
      • 当 master 被 kill,且 slave 升级为 master 时,就会出现主键冲突问题。
  • replace into 方法可能会导致部分数据丢失。

所以,实际使用时,若是唯一键冲突的情况,一定要谨慎,避免踩坑!

猜你喜欢

转载自blog.csdn.net/lijing742180/article/details/90243470