关于mysql:“INSERT IGNORE”vs“INSERT … ON DUPLICATE KEY UPDATE”

“INSERT IGNORE” vs “INSERT … ON DUPLICATE KEY UPDATE”

在执行包含许多行的INSERT语句时,我想跳过可能导致失败的重复条目。 经过一些研究,我的选择似乎是使用:

  • ON DUPLICATE KEY UPDATE意味着以某种代价进行不必要的更新,或者
  • INSERT IGNORE意味着邀请其他类型的未经宣告失败。

我对这些假设是对的吗? 简单地跳过可能导致重复的行并继续执行其他行的最佳方法是什么?


我建议使用INSERT...ON DUPLICATE KEY UPDATE

如果使用INSERT IGNORE,则实际上不会插入行,如果它导致重复键。但该声明不会产生错误。它会生成警告。这些案件包括:

  • 在具有PRIMARY KEYUNIQUE约束的列中插入重复键。
  • 将NULL插入到具有NOT NULL约束的列中。
  • 将行插入分区表,但插入的值不会映射到分区。

如果使用Replace,MySQL实际上会在内部执行DELETE后跟INSERT,这会产生一些意想不到的副作用:

  • 分配新的自动增量ID。
  • 可以删除具有外键的从属行(如果使用级联外键)或者阻止Replace
  • 不必要地执行触发DELETE的触发器。
  • 副作用也传播到复制从属。

更正:ReplaceINSERT...ON DUPLICATE KEY UPDATE都是MySQL特有的非标准专有发明。 ANSI SQL 2003定义了一个MERGE语句,可以解决相同的需求(以及更多),但MySQL不支持MERGE语句。

用户尝试编辑此帖子(编辑被主持人拒绝)。编辑尝试添加声明INSERT...ON DUPLICATE KEY UPDATE导致分配新的自动增量ID。确实生成了新的id,但是在更改的行中没有使用它。

请参阅下面的演示,使用Percona Server 5.5.28进行测试。配置变量innodb_autoinc_lock_mode=1(默认值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mysql> create table foo (id serial primary key, u int, unique key (u));
mysql> insert into foo (u) values (10);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   10 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1

mysql> insert into foo (u) values (10) on duplicate key update u = 20;
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1

上面演示了IODKU语句检测到重复,并调用更新来更改u的值。请注意,AUTO_INCREMENT=3表示已生成ID,但未在行中使用。

Replace确实删除原始行并插入新行,生成并存储新的自动增量ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+
mysql> replace into foo (u) values (20);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  3 |   20 |
+----+------+


如果你想看看这一切意味着什么,这里是一切的吹嘘:

1
2
3
4
5
6
CREATE TABLE `users_partners` (
  `uid` int(11) NOT NULL DEFAULT '0',
  `pid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`,`pid`),
  KEY `partner_user` (`pid`,`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

主键基于此快速参考表的两列。主键需要唯一值。

让我们开始:

1
2
3
4
5
6
7
8
9
10
11
12
INSERT INTO users_partners (uid,pid) VALUES (1,1);
...1 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1);
...0 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1) ON DUPLICATE KEY UPDATE uid=uid
...0 row(s) affected

请注意,上面通过设置列等于自身来节省了太多额外的工作,实际上不需要更新

1
2
REPLACE INTO users_partners (uid,pid) VALUES (1,1)
...2 row(s) affected

现在有一些多行测试:

1
2
3
4
5
6
INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...3 row(s) affected

在控制台中没有生成其他消息,现在它在表数据中有这4个值。除了(1,1)之外我删除了所有内容,所以我可以在同一个游戏区域进行测试

1
2
3
4
5
INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4) ON DUPLICATE KEY UPDATE uid=uid
...3 row(s) affected

REPLACE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...5 row(s) affected

所以你有它。由于这一切都是在一张几乎没有数据且没有生产的新桌子上进行的,因此执行的时间是微观的,无关紧要的。任何拥有真实数据的人都非常欢迎您提供。


要添加的重要内容:使用INSERT IGNORE并确实存在密钥违规时,MySQL不会发出警告!

如果您尝试一次插入100条记录,如果有一条有故障,您将进入交互模式:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

如你所见:没有警告!在官方的Mysql文档中甚至错误地描述了这种行为。

如果需要通知您的脚本,如果没有添加某些记录(由于密钥违规),您必须调用mysql_info()并解析它以获取"Duplicates"值。


我经常使用INSERT IGNORE,这听起来就像你正在寻找的那种行为。只要您知道不会插入会导致索引冲突的行并且您相应地规划您的程序,它就不会造成任何麻烦。


我知道这是旧的,但我会添加这个注释,以防其他人(像我)到达此页面,同时试图找到有关INSERT..IGNORE的信息。

如上所述,如果使用INSERT..IGNORE,则执行INSERT语句时发生的错误将被视为警告。

未明确提及的一件事是INSERT..IGNORE将导致无效值在插入时将被调整为最接近的值(而如果未使用IGNORE关键字,则无效值将导致查询中止)。


ON DUPLICATE KEY UPDATE不是真正的标准。它和REPLACE一样标准。请参见SQL MERGE。

基本上这两个命令都是标准命令的替代语法版本。


Replace进入似乎是一种选择。或者您可以查看

1
IF NOT EXISTS(QUERY) Then INSERT

这将插入或删除然后插入。我倾向于首先进行IF NOT EXISTS检查。


INSERT IGNORE的潜在危险。
如果您尝试插入更长的VARCHAR值,则定义了列 - 将截断并插入值即使启用严格模式也是如此。


如果要在表中插入主键或唯一索引的冲突,它将更新冲突行而不是插入该行。

句法:

1
insert into table1 set column1 = a, column2 = b on duplicate update column2 = c;

现在,这个insert语句可能看起来与前面看到的不同。此insert语句尝试将table1中的行与a和b的值分别插入到列column1和column2中。

让我们深入理解这个陈述:

例如:此处column1被定义为table1中的主键。

现在,如果在table1中,在column1中没有值为"a"的行。所以这个语句会在table1中插入一行。

现在,如果在table1中,在column2中有一行值为"a"。因此,此语句将使用"c"更新行的column2值,其中column1值为"a"。

因此,如果要插入新行,则在主键或唯一索引的冲突上更新该行。
阅读此链接的更多信息


如果在查询集的末尾使用带有SHOW WARNINGS;语句的insert ignore,则会显示包含所有警告的表,包括哪些ID是重复项。