为什么MySQL自增会在插入失败时增加?

52

我的同事刚刚让我注意到一个非常奇怪的MySQL行为。

假设您有一张带自增字段和另一个设为唯一的字段的表(例如,用户名字段)。当尝试插入一个已经存在于表中的用户名时,插入操作失败,这是预期的行为。然而,当您在多次插入失败后插入一个有效的新条目时,可以看到自增值已经增加了。

例如,当我们的最后一个条目看起来像这样时...

ID: 10
Username: myname

如果我们使用相同的用户名值尝试插入五个新条目,下一次插入时将会创建一个新行,如下所示:

ID: 16
Username: mynewname
虽然这本身不是一个大问题,但通过使用大量的失败插入请求来淹没表格似乎是一种非常愚蠢的攻击方式。正如MySQL参考手册所述: “如果[...]该值变得大于可存储在指定整数类型中的最大整数,则自动增量机制的行为未定义。”
这是否是预期行为?

7
您的攻击向量似乎不是问题。如果您可以使用失败的插入请求淹没它,那么您同样可以用未失败的请求淹没它,这并不成问题。 - Martin Smith
6
虽然这是正确的,但我认为突然涌现大量新用户会比自动递增的悄无声息的增长更加明显,如果不加检查,它很可能会被忽略。 - Sorcy
4个回答

39

InnoDB是一种事务型引擎。

这意味着在以下情况下:

  1. 会话A插入记录1
  2. 会话B插入记录2
  3. 会话A回滚

有可能存在一个间隙,或者会话B将锁定,直到会话A提交或回滚。

InnoDB的设计者(与大多数其他事务型引擎设计者一样)选择允许存在间隙。

文档中可以知道:

当访问自增计数器时,InnoDB使用特殊的表级别AUTO-INC锁,该锁保留到当前SQL语句的结束,而不是保留到事务的结束。引入特殊的锁释放策略是为了提高对包含AUTO_INCREMENT列的表进行插入时的并发性能。

InnoDB在服务器运行期间使用内存中的自增计数器。当服务器停止并重新启动时,InnoDB为每个表的第一个INSERT重新初始化计数器,如前所述。

如果你担心id列会被回卷,则将其设置为BIGINT(8字节长)。


2
你的回答对我的问题帮助很大,谢谢。有没有办法绕过InnoDB关于自增的这种行为? - Aufwind
1
@Ankit:请将其作为另一个问题发布,并在此处放置链接。 - Quassnoi
如您所请求:其他问题 - Damian Yerrick

5
不了解具体内部情况,但我认为自动增量应该允许由于插入失败而跳过值。比如你正在进行银行交易或其他需要将整个交易和多条记录作为一体的操作。如果你尝试插入并获取一个ID,然后使用该交易ID对所有后续详细信息进行标记并插入详细记录,你需要确保资格唯一性。如果有多个人同时访问数据库,他们也需要确保自己获得自己的交易ID,以便在提交事务时不与您的ID发生冲突。如果第一次交易失败,没有任何损失,也没有下游挂起的元素。

这是自动递增的跳过性质,但我想知道是否有任何方法可以停止这种跳过。 - user1285324
2
@Ankit,不,你不能停止跳过。记录上一个分配的ID的文件头始终会递增。如果出现问题,有10个人输入交易并且3个人中止,您永远不希望回溯这样的中止交易只是为了使用ID。 - DRapp

4

虽然这篇文章有点旧了,但它可能会帮助到人们。如果您需要设置 innodb_autoinc_lock_mode 参数,请将其设置为0或2

可指定数值的系统变量可以在命令行上以--var_name=value格式或在选项文件中以var_name=value格式指定。

命令行参数格式:

--innodb-autoinc-lock-mode=0 

或者 打开你的mysql.ini文件并添加以下行:

innodb_autoinc_lock_mode=0

-3

我知道这是一篇旧文章,但由于我也找不到正确的答案,我实际上找到了一种方法来解决这个问题。您必须将查询包装在if语句中。通常是插入查询或插入和重复查询会破坏有序的自动递增顺序,因此对于常规插入,请使用:

$check_email_address = //select query here\\

if ( $check_email_address == false ) {
    your query inside of here
}

而不是使用INSERT AND ON DUPLICATE,可以在if语句内或外使用UPDATE SET WHERE QUERY,并且REPLACE INTO QUERY也似乎可行。


7
我正在对这个答案进行负评,因为它根本不适用。是的,您可以在尝试插入之前检查记录是否存在,但这完全无关紧要。 - Mave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接