903

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

  • ON DUPLICATE KEY UPDATE这意味着以某种代价进行不必要的更新,或者
  • INSERT IGNORE暗示其他类型的失败会在未经通知的情况下溜进来。

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

4

12 回答 12

1065

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

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

  • PRIMARY KEY在具有orUNIQUE约束 的列中插入重复键。
  • 将 NULL 插入具有NOT NULL约束的列中。
  • 向分区表插入一行,但插入的值未映射到分区。

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

  • 分配了一个新的自增 ID。
  • 可能会删除具有外键的相关行(如果您使用级联外键),否则会阻止REPLACE.
  • 触发的触发器DELETE被不必要地执行。
  • 副作用也会传播到副本。

更正:两者都是 MySQL 特有REPLACEINSERT...ON DUPLICATE KEY UPDATE非标准专有发明。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 |
+----+------+
于 2009-02-14T05:51:40.427 回答
189

如果您想了解这一切意味着什么,这里将逐一介绍所有内容:

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

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

于 2011-10-21T18:23:12.367 回答
45

需要补充的重要一点:当使用 INSERT IGNORE 并且您确实有密钥违规时,MySQL 不会发出警告!

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

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

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

如果您的脚本需要被通知,如果某些记录没有被添加(由于键违规),您必须调用 mysql_info() 并将其解析为“Duplicates”值。

于 2011-04-21T10:04:52.253 回答
24

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

于 2009-02-14T05:53:59.517 回答
22

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

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

于 2010-09-16T14:48:18.533 回答
10

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

IF NOT EXISTS(QUERY) Then INSERT

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

于 2009-02-14T05:34:20.687 回答
7

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

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

于 2009-02-14T05:57:00.227 回答
7

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

于 2017-10-19T23:33:58.147 回答
4

如果在查询集末尾使用语句将显示一个包含所有警告的表,包括哪些 ID 是重复的insert ignoreSHOW WARNINGS;

于 2017-03-13T14:19:06.003 回答
4

添加到此。如果你在同一个语句中同时使用两者 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';
于 2020-07-03T22:46:07.243 回答
2

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上的评论。

于 2019-11-29T23:26:02.917 回答
1

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

句法:

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

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

让我们深入理解这个说法:

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

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

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

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

于 2017-12-07T21:00:10.857 回答