【问题标题】:Prevent auto increment on MySQL duplicate insert防止 MySQL 重复插入自动递增
【发布时间】:2011-08-20 22:51:34
【问题描述】:

使用 MySQL 5.1.49,我正在尝试实现一个标记系统 我遇到的问题是一个有两列的表:id(autoincrement)tag(unique varchar) (InnoDB)

当使用查询 INSERT IGNORE INTO tablename SET tag="whatever" 时,即使插入被忽略,自动递增 id 的值也会增加。

通常这不会是一个问题,但我希望有很多可能的尝试为这个特定的表插入重复项,这意味着我的新行的 id 字段的下一个值会跳跃太多。

例如,我最终会得到一个有 3 行但不好 id's 的表格

1   | test
8   | testtext
678 | testtextt

另外,如果我不执行 INSERT IGNORE 而是执行常规 INSERT INTO 并处理错误,自动增量字段仍会增加,因此下一个真正的插入仍然是错误的自动增量。

如果有 INSERT 重复行尝试,有没有办法停止自动递增?

据我了解,对于 MySQL 4.1,此值不会增加,但我想做的最后一件事是提前执行大量 SELECT 语句以检查标签是否存在,或者更糟糕的是,降级我的 MySQL 版本。

【问题讨论】:

  • 你在使用 InnoDB 吗?如果是这样,请参阅*.com/questions/2787910/…
  • 啊是的。我只是做了一个快速编辑。我会检查链接tnx
  • 只是对该链接的跟进。可悲的是,它并没有真正解决问题,而是尝试为 id 字段使用 bigint 以避免表爆炸。不过谢谢

标签: mysql insert duplicates innodb auto-increment


【解决方案1】:

您可以随时添加ON DUPLICATE KEY UPDATE Read here(不完全是,但似乎可以解决您的问题)。

来自 cmets,作者:@ravi

是否发生增量取决于 innodb_autoinc_lock_mode 设置。如果设置为非零值,则 即使 ON DUPLICATE KEY 触发,auto-inc 计数器也会增加

【讨论】:

  • @robert - 如果您在该表上有一个唯一键,并且您尝试使用 ...ON DUPLICATE... 和相同的唯一键插入,则它不能增加,如果确实如此,要么我们误解了对方,要么你的 MySQL 有错误?
  • 不。它确实增加了。如果以下语句有重复 ----- INSERT INTO tags (tag_text) VALUES (cur_string) ON DUPLICATE KEY UPDATE tag_text=cur_string ----------- 那么下一个不重复的实际插入将有问题中描述的ID。我认为这更像是 mysql 部分的功能而不是错误。虽然有点奇怪
  • @robert - 有趣,我经常使用这个,你使用的是什么版本的 MySQL(如果可以的话,确切的一个)?
  • mysql 5.1.49,表是innodb。您可能正在使用 mysql 4.1,我认为这是非递增自动增量实际上被视为错误的版本。 O.o
  • 是否发生增量取决于innodb_autoinc_lock_mode 设置。如果设置为非零值,即使 ON DUPLICATE KEY 触发,auto-inc 计数器也会增加。
【解决方案2】:

你可以修改你的 INSERT 是这样的:

INSERT INTO tablename (tag)
SELECT $tag
FROM tablename
WHERE NOT EXISTS(
    SELECT tag
    FROM tablename
    WHERE tag = $tag
)
LIMIT 1

$tag 是您想要添加的标签(正确引用或作为占位符),如果它不存在的话。如果标签已经存在,这种方法甚至不会触发 INSERT(以及随后的自动增量浪费)。您可能会想出比这更好的 SQL,但上面应该可以解决问题。

如果您的表被正确索引,那么用于存在检查的额外 SELECT 将很快,并且无论如何数据库都必须执行该检查。

这种方法不适用于第一个标签。您可以为您的标签表添加一个您认为最终会被使用的标签,或者您可以单独检查一个空表。

【讨论】:

  • 试图避免这种情况,但我有一个想法。在每次插入后执行以下 sql 语句。它将 id 的自动增量重置为实际使用的最后一个。 ALTER TABLE tags AUTO_INCREMENT = 1 这可能有问题。它似乎会导致所有行的更新,但 ids 没有改变。它返回受影响的行 xx,即表中的记录数。除此之外,我可以看到有一个虚拟表存储标签的数量,并且在添加新标签时会更新计数器。因此,我将管理自己的 ID,而不是使用 autoincrement 。
  • @robert:如果您尝试管理自己的 ID,将会遇到并发问题。或者你必须做一堆表锁定来模拟对自动递增的受限访问。为什么要避免在插入之前检查重复项?您是否检查过是否存在真正的性能影响?
  • 不,我想没什么太严重的,这似乎是一种不好的做法。说起来,ALTER TABLE标签AUTO_INCREMENT = 1的执行时间更差。
  • 很好的解决方案,最初插入一行时遇到了一些麻烦。我发现如果你选择的表是空的,插入就不会发生。
  • @Jbrown:你是对的,空表可能是个问题。我认为最简单的做法是在创建表时手动将标签添加到表中,这是您知道会被使用的东西。通常我只是走“添加唯一约束,盲目插入标签,捕获/忽略预期错误”的路线,而不用担心自动增量值。
【解决方案3】:

我刚刚发现了这颗宝石......

http://www.timrosenblatt.com/blog/2008/03/21/insert-where-not-exists/

INSERT INTO [table name] SELECT '[value1]', '[value2]' FROM DUAL
WHERE NOT EXISTS(
    SELECT [column1] FROM [same table name]
    WHERE [column1]='[value1]'
    AND [column2]='[value2]' LIMIT 1
)

如果affectedRows = 1 那么它插入了;否则如果affectedRows = 0,则存在重复。

【讨论】:

  • 不错。与 Landon 的解决方案基本相同,但使用 DUAL 假表更优雅。
  • 这应该是公认的答案,因为即使表为空它也可以工作。谢谢!
【解决方案4】:

我遇到了同样的问题,但不想使用 innodb_autoinc_lock_mode = 0,因为感觉就像我在用榴弹炮杀死一只苍蝇。

为了解决这个问题,我最终使用了一个临时表。

create temporary table mytable_temp like mytable;

然后我插入值:

insert into mytable_temp values (null,'valA'),(null,'valB'),(null,'valC');

之后,您只需进行另一次插入,但使用“not in”来忽略重复项。

insert into mytable (myRow) select mytable_temp.myRow from mytable_temp 
where mytable_temp.myRow not in (select myRow from mytable);

我还没有测试过它的性能,但它确实有效并且易于阅读。当然,这很重要,因为我正在处理不断更新的数据,所以我不能忽视这些差距。

【讨论】:

    【解决方案5】:

    v 5.5 的 MySQL 文档说:

    "If you use INSERT IGNORE and the row is ignored, the AUTO_INCREMENT counter 
    is **not** incremented and LAST_INSERT_ID() returns 0, 
    which reflects that no row was inserted."
    

    参考:http://dev.mysql.com/doc/refman/5.5/en/information-functions.html#function_last-insert-id

    自 5.1 版以来,InnoDB 具有可配置的自动增量锁定。另见http://dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html#innodb-auto-inc...

    解决方法:使用选项 innodb_autoinc_lock_mode=0(传统)。

    【讨论】:

    • 有趣的是,在非传统的情况下,对于 MySQL 5.5.41 使用INSERT..ON DUPLICATE KEY UPDATE,它仍然会增加
    • 如果您使用 INSERT IGNORE 并且该行被忽略,则 LAST_INSERT_ID() 与当前值保持不变(如果连接尚未执行成功 INSERT,则返回 0)如果动作没有执行
    【解决方案6】:

    我发现 mu 的回答太短很有帮助,但由于它不会在空表上进行插入操作,所以会受到限制。我发现一个简单的修改就可以了:

    INSERT INTO tablename (tag)
    SELECT $tag
    FROM (select 1) as a     #this line is different from the other answer
    WHERE NOT EXISTS(
        SELECT tag
        FROM tablename
        WHERE tag = $tag
    )
    LIMIT 1
    

    将 from 子句中的表替换为“假”表(select 1) as a 允许该部分返回允许插入发生的记录。我正在运行 mysql 5.5.37。感谢 mu 让我一路走好....

    【讨论】:

    • @langla:不,它没有。这是我的错:“tag”是列名,“$tag”是列值:-)
    • 非常好,谢谢。有一段时间想不通为什么我的代码不能处理空表。
    【解决方案7】:

    接受的答案很有用,但是我在使用它时遇到了一个问题,基本上如果您的表没有条目,它将无法工作,因为选择正在使用给定的表,所以我想出了以下内容,这将即使表格为空,也只需将表格插入2处,将变量插入1处,少出错。

    INSERT INTO database_name.table_name (a,b,c,d)
    SELECT 
        i.*
    FROM
        (SELECT 
            $a AS a, 
                $b AS b,
                $c AS c,
                $d AS d
                /*variables (properly escaped) to insert*/
        ) i
            LEFT JOIN        
        database_name.table_name o ON i.a = o.a AND i.b = o.b /*condition to not insert for*/
    WHERE
        o.a IS NULL
    LIMIT 1 /*Not needed as can only ever be one, just being sure*/
    

    希望对你有用

    【讨论】:

      【解决方案8】:

      我只是在插入/更新查询之后添加了一个额外的语句: 更改表table_name AUTO_INCREMENT = 1 然后他自动选择最高的prim key id加1。

      【讨论】:

        【解决方案9】:

        修改了 mu 的答案太短,(只删除一行) 因为我是新手,所以我不能在他的回答下面发表评论。直接贴在这里

        下面的查询适用于第一个标签

         INSERT INTO tablename (tag)
         SELECT $tag
         WHERE NOT EXISTS(
            SELECT tag
            FROM tablename
            WHERE tag = $tag
        )
        

        【讨论】: