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

时间:2009-02-14 05:24:07

标签: mysql insert

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

  • ON DUPLICATE KEY UPDATE这意味着需要花费一些不必要的更新,或者
  • INSERT IGNORE意味着邀请其他类型的未能在未事先通知的情况下失败。

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

12 个答案:

答案 0 :(得分:957)

我建议使用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(默认值):

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 |
+----+------+

答案 1 :(得分:166)

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

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

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

答案 2 :(得分:39)

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

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

  

Query OK, 99 rows affected (0.04 sec)

     

Records: 100 Duplicates: 1 Warnings: 0

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

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

答案 3 :(得分:18)

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

答案 4 :(得分:17)

我知道这已经过时了,但是我会添加这个注释,以防其他人(比如我)在尝试查找有关INSERT..IGNORE的信息时到达此页面。

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

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

答案 5 :(得分:8)

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

IF NOT EXISTS(QUERY) Then INSERT

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

答案 6 :(得分:8)

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

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

答案 7 :(得分:3)

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

答案 8 :(得分:2)

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

答案 9 :(得分:2)

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

<强>语法:

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

现在,这个插入语句可能与您之前看到的不同。这个insert语句试图将table1中的一行和a和b的值分别插入到列column1和column2中。

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

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

现在,如果在table1中,第1列中没有值为“a”的行。所以这个语句会在table1中插入一行。

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

因此,如果要插入新行,则在主键或唯一索引的冲突上更新该行 Read more on this link

答案 10 :(得分:1)

添加到此。如果您在同一条语句中同时使用 INSERT IGNOREON DUPLICATE KEY UPDATE,如果插入物发现重复的键,则更新仍会发生。换句话说,更新优先于忽略。但是,如果ON DUPLICATE KEY UPDATE子句本身导致重复的键错误,则该错误将被忽略。

如果您拥有多个唯一键,或者您的更新尝试违反外键约束,则会发生这种情况。

CREATE TABLE test 
 (id BIGINT (20) UNSIGNED AUTO_INCREMENT, 
  str VARCHAR(20), 
  PRIMARY KEY(id), 
  UNIQUE(str));

INSERT INTO test (str) VALUES('A'),('B');

/* duplicate key error caused not by the insert, 
but by the update: */
INSERT INTO test (str) VALUES('B') 
 ON DUPLICATE KEY UPDATE str='A'; 

/* duplicate key error is suppressed */
INSERT IGNORE INTO test (str) VALUES('B') 
 ON DUPLICATE KEY UPDATE str='A';

答案 11 :(得分:0)

首选

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

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

就我而言,我知道col1col2构成唯一的复合索引。

它跟踪错误,但不会在重复项上引发异常。 关于性能,与MySQL notices this and does not update it

相同值的更新是有效的
INSERT INTO table
  (col1, col2, col3, col4)
VALUES
  (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
    col1 = VALUES(col1),
    col2 = VALUES(col2)

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