【问题标题】:Why does MySQL autoincrement increase on failed inserts?为什么 MySQL 自动增量会在插入失败时增加?
【发布时间】:2011-02-16 18:59:09
【问题描述】:

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

假设您有一个表,其中包含一个 auto_increment 字段和另一个设置为唯一字段(例如用户名字段)。当尝试使用表中已经存在的用户名插入一行时,插入失败,正如预期的那样。然而,当您在多次尝试失败后插入有效的新条目时,可以看到 auto_increment 值增加了。

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

ID: 10
Username: myname

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

ID: 16
Username: mynewname

虽然这本身不是一个大问题,但正如 MySQL 参考手册所述,通过将失败的插入请求淹没表来杀死表似乎是一个非常愚蠢的攻击向量:

“如果 [...] 值变得大于可以存储在指定整数类型中的最大整数,则未定义自动增量机制的行为。”

这是预期的行为吗?

【问题讨论】:

  • 您的攻击媒介似乎不是问题。如果你可以用失败的插入请求来淹没它,你不能同样用非失败的请求来淹没它吗?
  • @martin smith:虽然这是真的,但我认为新用户的突然激增比 auto_increment 的无声增加更为明显,如果不检查它很可能会被淘汰。

标签: mysql innodb auto-increment


【解决方案1】:

InnoDB 是一个事务引擎。

这意味着在以下场景中:

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

,要么存在间隙,要么session B 将锁定直到session A 提交或回滚。

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

来自documentation

在访问自动递增计数器时,InnoDB 使用特殊的表级AUTO-INC 锁,它保持到当前SQL 语句的末尾,而不是事务的末尾。引入了特殊的锁释放策略来提高插入包含AUTO_INCREMENT 列的表的并发性

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

如果您害怕 id 列环绕,请将其设为 BIGINT(8 字节长)。

【讨论】:

  • 您的回答对我自己的问题帮助很大。谢谢你。有没有办法绕过 InnoDB 关于自动增量的这种行为?
  • @Ankit:请将其作为另一个问题发布并在此处放置链接。
  • 如您所愿:Other questions
【解决方案2】:

如果不知道确切的内部结构,我会说是的,自动增量应该允许跳过的值对失败的插入进行。假设您正在进行银行交易,或者整个交易和多条记录作为全有或全无的其他方式。如果您尝试插入,获取一个 ID,然后用该事务 ID 标记所有后续详细信息并插入详细记录,则需要确保您的合格唯一性。如果您有多个人猛烈抨击数据库,他们也需要确保他们获得自己的事务 ID,以免在提交事务时与您的事务 ID 冲突。如果第一次交易失败,则不会造成任何损害,也不会在下游产生悬空元素。

【讨论】:

  • 它使用了这种自动增量的 skkiping 特性,但我想知道这是阻止这种 skkiping 的任何方法
  • @Ankit,不,你不能停止跳过。跟踪分配的最后一个 ID 的文件头总是递增。如果出现问题,并且 10 人正在输入交易并且 3 人中止,那么您永远不会想要回填此类中止的交易以使用 ID。
【解决方案3】:

旧帖, 但这可能对人们有所帮助, 您可能需要将 innodb_autoinc_lock_mode 设置为 0 或 2

采用数值的系统变量可以在命令行中指定为--var_name=value,或者在选项文件中指定为var_name=value

命令行参数格式:

--innodb-autoinc-lock-mode=0 

打开你的 mysql.ini 并添加以下行:

innodb_autoinc_lock_mode=0

【讨论】:

    【解决方案4】:

    我知道这是一篇旧文章,但由于我也找不到正确的答案,我实际上找到了一种方法。您必须将查询包装在 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 似乎也有效

    【讨论】:

    • 我不赞成这个答案,因为它根本不适用。是的,您可以在尝试插入之前检查记录是否存在,但这完全不是重点。
    猜你喜欢
    • 2012-04-25
    • 2012-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多