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

ON DUPLICATE KEY UPDATE,这意味着在某些代价下进行不必要的更新,或者 INSERT IGNORE暗示着邀请其他类型的失败在未通知的情况下潜入。

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


当前回答

替换成似乎是一个选择。或者你可以查一下

IF NOT EXISTS(QUERY) Then INSERT

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

其他回答

我推荐使用INSERT…重复密钥更新。

如果使用INSERT IGNORE,那么如果它产生了重复的键,那么该行实际上不会被插入。但是该语句不会生成错误。相反,它生成一个警告。这些情况包括:

在具有PRIMARY key或UNIQUE约束的列中插入重复键。 在具有NOT NULL约束的列中插入NULL。 向分区表插入一行,但插入的值不映射到分区。

如果你使用REPLACE, MySQL实际上在内部执行了一个DELETE,然后是一个INSERT,这有一些意想不到的副作用:

分配一个新的自动递增ID。 带有外键的依赖行可能会被删除(如果使用级联外键),或者阻止REPLACE。 在DELETE上触发的触发器被不必要地执行。 副作用也会传播到副本。

修正:替换和插入…ON DUPLICATE KEY UPDATE是MySQL特有的非标准专利发明。ANSI SQL 2003定义了一个MERGE语句,可以解决同样的需求(以及更多),但MySQL不支持MERGE语句。


一个用户试图编辑这篇文章(编辑被版主拒绝了)。编辑试图添加一个声明,插入…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 |
+----+------+

需要补充的重要一点是:当使用INSERT IGNORE时,如果你确实违反了键,MySQL不会发出警告!

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

查询OK,影响99行(0.04秒) 记录:100重复:1警告:0

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

如果你的脚本需要被告知,如果一些记录没有被添加(由于键违反),你必须调用mysql_info()并解析它的“duplicate”值。

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

如果你想知道这一切意味着什么,下面是所有事情的详细说明:

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

结果出来了。由于这一切都是在一个几乎没有数据的新表上执行的,而且不在生产环境中,因此执行的时间非常短,而且不相关。任何拥有真实数据的人都非常欢迎贡献这些数据。

插入……ON DUPLICATE KEY UPDATE优先用于防止意外异常管理。

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

在我的例子中,我知道col1和col2组成了一个唯一的综合指数。

它会跟踪错误,但不会对副本抛出异常。 关于性能,相同值的更新是有效的,因为MySQL注意到这一点,并且不更新它

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

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