【问题标题】:"INSERT IGNORE" vs "INSERT ... ON DUPLICATE KEY UPDATE"“INSERT IGNORE”与“INSERT ... ON DUPLICATE KEY UPDATE”
【发布时间】:2010-10-07 14:32:47
【问题描述】:

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

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

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

【问题讨论】:

    标签: mysql insert


    【解决方案1】:

    如果您想了解这一切意味着什么,这里是所有内容的详细介绍:

    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】:

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

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

      【讨论】:

        【解决方案3】:

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

        【讨论】:

          【解决方案4】:

          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 的 cmets。

          【讨论】:

            【解决方案5】:

            我建议使用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 |
            +----+------+
            

            【讨论】:

            • INSERT IGNORE 也会增加自动增量值,即使它没有插入任何内容。
            • 实际上,(我不能再编辑我的评论了)INSERT IGNORE 也可以在插入因同一个键以外的其他原因失败时增加自动增量值,例如如果值u 必须是唯一的
            • 是的,没错。
            【解决方案6】:

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

            语法:

            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

            【讨论】:

              【解决方案7】:

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

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                • SHOW WARNINGS; 似乎只影响最新的查询。如果您有多个语句,则不会累积任何先前的语句。
                【解决方案9】:

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

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

                Query OK, 99 rows affected (0.04 sec)

                Records: 100 Duplicates: 1 Warnings: 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 时,重复键将被忽略,不会出现错误或警告。
                【解决方案10】:

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

                IF NOT EXISTS(QUERY) Then INSERT
                

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

                【讨论】:

                  【解决方案11】:

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

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

                  【讨论】:

                    【解决方案12】:

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

                    【讨论】:

                      猜你喜欢
                      • 2015-07-30
                      • 2023-02-06
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-10-03
                      • 2016-10-29
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-01-29
                      相关资源
                      最近更新 更多