ChatGPT解决这个技术问题 Extra ChatGPT

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

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

ON DUPLICATE KEY UPDATE 这意味着以一定成本进行不必要的更新,或

INSERT IGNORE 暗示其他类型的失败会在未经通知的情况下溜进来。

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


B
Bill Karwin

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

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

在具有 PRIMARY KEY 或 UNIQUE 约束的列中插入重复键。

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

INSERT IGNORE 也会增加自动增量值,即使它没有插入任何内容。
实际上,(我不能再编辑我的评论了)INSERT IGNORE 还可以在插入因同一个键以外的其他原因失败时增加自动增量值,例如,如果值 u 必须是唯一的
是的,这是正确的。
P
Paulus Maximus

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

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

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


C
Community

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

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

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

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

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


如果您使用的是 PHP,则需要使用 mysqli_affected_rows() 来了解 INSERT 是否实际发生。
对于 MySQL 5.5 和 MariaDB 10,我 确实 得到一个错误 Cannot add or update a child row: a foreign key constraint fails 并且没有添加行(即使是有效行)。
@Floris 该错误是由于外键约束而不是由于重复键。我正在使用 MySQL 5.5.28。使用 INSERT IGNORE 时,重复键将被忽略,不会出现错误或警告。
D
David Z

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


S
SherylHohman

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

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


S
Shafizadeh

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

IF NOT EXISTS(QUERY) Then INSERT

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


L
LOL

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


C
Chris KL

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

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


j
joshstrike

添加到此。如果您在同一语句中同时使用 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';

R
Ray Foss

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


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

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


J
JonathanDavidArndt

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

句法:

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”。

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