“ INSERT IGNORE”和“ INSERT…在重复密钥更新上”


833

在执行INSERT包含多行的语句时,我想跳过重复的条目,否则它们会导致失败。经过研究后,我的选择似乎是使用以下任一方法:

  • ON DUPLICATE KEY UPDATE 这意味着要付出一定代价进行不必要的更新,或者
  • INSERT IGNORE 这暗示了其他类型的失败的邀请。

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

Answers:


990

我建议使用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可以满足相同需求(甚至更多)的MERGE语句,但是MySQL不支持该语句。


一位用户尝试编辑此帖子(主持人拒绝了该编辑)。修改尝试添加一个声明,该声明INSERT...ON DUPLICATE KEY UPDATE导致分配了新的自动递增ID。确实会生成新的id ,但是更改后的行中不会使用它。

请参见下面的演示,该演示已通过Percona Server 5.5.28测试。配置变量innodb_autoinc_lock_mode=1(默认):

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:

mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+
mysql> replace into foo (u) values (20);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  3 |   20 |
+----+------+

3
我想知道mysql开发团队是否打算采用ANSI SQL 2003中的MERGE吗?
Lonnie Best

1
@LonnieBest:实施MERGE的功能请求是在2005年提出的,但据我所知尚无进展或计划。 bugs.mysql.com/bug.php?id=9018
Bill Karwin

2
哦,我可能会补充说,它会为无效类型不匹配生成警告(不是错误),但不会为重复的复合主键生成警告。
法布里西奥磨砂

11
我一直在查看一个由很多INSERT ... ON DUPLICATE KEY UPDATE ...语句填充的表。许多数据是重复的,这导致AI PK的一个实例在两行之间从17,029,941增加到46,271,740。每次产生新的AI意味着您的范围可以很快被填满,您需要清理。这个桌子只有两个星期大了!
Engineer81

4
@AntTheKnee,啊,大数据时代的工作挑战。
Bill Karwin 2014年

174

如果您想了解所有这些内容,以下是所有内容的逐项记录:

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

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

让我们开始:

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

请注意,以上方法通过将列设置为等于自身而节省了过多的额外工作,实际上无需更新

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

现在进行一些多行测试:

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)以外的所有内容,因此可以在相同的游戏环境中进行测试

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

所以你有它。由于这些都是在几乎没有数据且不在生产中的新鲜表上执行的,因此执行时间是微观的且无关紧要的。任何拥有真实数据的人都将非常乐意为您提供数据。


我在重复的密钥上都运行并替换为。我的表以〜120K行结尾,其中约30%是重复行。在重复上,密钥运行了102秒,替换密钥运行了105秒。就我而言,我坚持使用重复键。
脚踝炎

1
在MariaDB 10上测试了以上内容,并在运行时收到警告INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
弗洛里斯2014年

您为此使用了哪个MySQL版本?
Radu Murzea '16

41

需要添加的重要信息:使用INSERT IGNORE并且确实存在键冲突时,MySQL不会发出警告!

例如,如果您尝试一次插入100条记录,其中一条出现错误,则将进入交互模式:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

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

如果需要通知脚本,如果未添加某些记录(由于键冲突),则必须调用mysql_info()并将其解析为“ Duplicates”值。


6
如果您使用的是PHP,则需要使用mysqli_affected_rows()了解INSERT实际发生的情况。
阿马尔·穆拉利

随着MySQL的两个5.5和MariaDB的10我得到一个错误Cannot add or update a child row: a foreign key constraint fails ,且行(即使有效的)添加。
弗洛里斯2014年

2
@Floris该错误是由于外键约束而不是由于重复的键引起的。我正在使用MySQL 5.5.28。使用时INSERT IGNORE,重复的键将被忽略,没有错误或警告。
toxalot 2014年

20

我通常使用INSERT IGNORE,这听起来也正是您要查找的行为。只要您知道将不会插入会导致索引冲突的行,并据此计划程序,就不会造成任何麻烦。


4
我担心我会忽略重复以外的错误。这是正确的还是INSERT IGNORE仅忽略而仅忽略复制失败?谢谢!
Thomas G Henry 2009年

2
它将任何错误变为警告。请参阅我的答案中的此类案例列表。
比尔·卡文

真可惜 我希望它只会忽略重复的失败。
Lonnie Best

违反按键会导致错误!请参阅@Jens的回答。
弗洛里斯

1
@Pacerier,取决于您的应用程序是否检查警告。或者它是否可以检查警告。例如,大多数ORM软件包都没有给您机会。一些连接器(例如JDBC)也将您与MySQL API隔离开来,因此您没有机会检查警告。
Bill Karwin 2015年

18

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

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

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


6
我不太确定您所说的“无效值”是什么意思,并且更正为什么意思?您能否提供示例或进一步的解释?
马伦兹2011年

4
这意味着,如果在使用“ INSERT IGNORE”时在字段中插入错误的数据类型,则将修改数据以匹配该字段的数据类型,并且将插入一个可能无效的值,然后查询将继续运行。仅使用“ INSERT”,将引发有关错误数据类型的错误,并且查询将被中止。将数字插入varchar或文本字段中可能会很好,但是将文本字符串插入具有数字数据类型的字段中会导致数据损坏。
codewaggle 2012年

2
@Marenz另一个示例:如果您的表具有非空列,并且您的“ INSERT IGNORE”查询未为该列指定值,则无论是否启用了严格的sql_mode,行都将在该列中插入零值。
香农

关于无效值的要点!该线程非常适合用于学习“ INSERT IGNORE”,我也将留下5美分:medium.com/legacy-systems-diary/…不错的文章,其中举例说明了在使用“ INSERT IGNORE”时应多加小心声明。
0x49D1

8

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

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


1
replace会执行删除和插入操作,而重复的键更新会更新现有行。一些区别是:自动递增id,行位置,一堆触发器
ahnbizcad

8

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

IF NOT EXISTS(QUERY) Then INSERT

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


感谢您的快速回复。我假设到处都是,但是我假设这与ON DUPLICATE KEY UPDATE类似,因为它将执行不必要的更新。它看起来很浪费,但我不确定。这些都应该起作用。我想知道是否有人知道哪个最好。
Thomas G Henry 2009年

6
NTuplip-该解决方案仍然可以通过并发事务从插入中竞争条件。
克里斯KL,2009年

REPLACE删除表中所有具有匹配的any PRIMARYUNIQUEkey的行,然后 删除INSERTs。与IODKU相比,这可能需要做更多的工作。
里克·詹姆斯

4

INSERT IGNORE的潜在危险。如果尝试插入VARCHAR值的时间更长,则用-定义了列-该值将被截断,并且即使启用了严格模式也可以插入。


3

如果使用insert ignore具有SHOW WARNINGS;在查询集的末尾账单会显示一个表,所有的警告,包括哪个ID是重复的。


SHOW WARNINGS;似乎只影响最新的查询。如果您有多个语句,则不会累积任何先前的语句。
卡乌

2

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

句法:

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

现在在这里,此插入语句可能看起来与您之前看到的有所不同。该插入语句试图将table1中具有a和b值的行分别插入到column1和column2列中。

让我们深入了解以下语句:

例如:这里column1被定义为table1中的主键。

现在,如果在表1中,在列1中没有行具有值“ a”。因此,该语句将在table1中插入一行。

现在,如果在表1中,在列2中存在一行具有值“ a”的行。因此,此语句将使用“ c”更新行的column2值,其中column1值为“ a”。

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


0

INSERT...ON DUPLICATE KEY UPDATE 最好防止意外的异常管理。

仅当您具有** 1个唯一约束**时,此解决方案才有效

就我而言,我知道这一点,col1col2可以创建唯一的复合索引。

它跟踪错误,但不会在重复项上引发异常。关于性能,以相同的值进行更新是有效的,因为MySQL注意到了这一点,并且不进行更新

INSERT INTO table
  (col1, col2, col3, col4)
VALUES
  (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
    col1 = VALUES(col1),
    col2 = VALUES(col2)

使用这种方法的想法来自phpdelusions.net/pdo的评论。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.