在执行INSERT
包含许多行的语句时,我想跳过可能导致失败的重复条目.经过一些研究,我的选择似乎是使用:
ON DUPLICATE KEY UPDATE
这意味着需要花费一些不必要的更新,或者
INSERT IGNORE
这意味着邀请其他类型的未能在未经宣布的情况下失败.
我对这些假设是对的吗?简单地跳过可能导致重复的行并继续执行其他行的最佳方法是什么?
我建议使用INSERT...ON DUPLICATE KEY UPDATE
.
如果使用INSERT IGNORE
,则实际上不会插入行,如果它导致重复键.但该声明不会产生错误.它会生成警告.这些案件包括:
在带有PRIMARY KEY
或UNIQUE
约束的列中插入重复键.
将NULL插入带有NOT NULL
约束的列中.
将行插入分区表,但插入的值不会映射到分区.
如果您使用REPLACE
,MySQL实际上会执行一个内部DELETE
跟随INSERT
,这有一些意想不到的副作用:
分配新的自动增量ID.
可以删除具有外键的从属行(如果使用级联外键)或者阻止REPLACE
.
触发的触发器DELETE
不必要地执行.
副作用也传播到复制从属.
修正:既REPLACE
和INSERT...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 | +----+------+
如果你想看看这一切意味着什么,这里是一切的吹嘘:
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
所以你有它.由于这一切都是在一张几乎没有数据且没有生产的新桌子上进行的,因此执行的时间是微观的,无关紧要的.任何拥有真实数据的人都非常欢迎您提供.
要添加的重要内容:使用INSERT IGNORE并确实存在密钥违规时,MySQL不会发出警告!
如果您尝试一次插入100条记录,如果有一条有错误,您将进入交互模式:
Query OK, 99 rows affected (0.04 sec)
Records: 100 Duplicates: 1 Warnings: 0
如你所见:没有警告!在官方的Mysql文档中甚至错误地描述了这种行为.
如果需要通知您的脚本,如果没有添加某些记录(由于密钥违规),您必须调用mysql_info()并解析它以获取"Duplicates"值.
我经常使用INSERT IGNORE
,这听起来就像你正在寻找的那种行为.只要您知道不会插入会导致索引冲突的行并且您相应地规划您的程序,它就不会造成任何麻烦.
我知道这是旧的,但我会添加这个注释,以防其他人(像我一样)到达此页面时尝试查找有关INSERT..IGNORE的信息.
如上所述,如果使用INSERT..IGNORE,则执行INSERT语句时发生的错误将被视为警告.
没有明确提到的一件事是INSERT..IGNORE将导致无效值在插入时将被调整为最接近的值(而如果未使用IGNORE关键字,则无效值将导致查询中止).
Replace
进入似乎是一种选择.或者您可以查看
IF NOT EXISTS(QUERY) Then INSERT
这将插入或删除然后插入.我倾向于先去IF NOT EXISTS
检查.
ON DUPLICATE KEY UPDATE不是真正的标准.它和REPLACE一样标准.请参见SQL MERGE.
基本上这两个命令都是标准命令的替代语法版本.