【问题标题】:MySQL AUTO_INCREMENT does not ROLLBACKMySQL AUTO_INCREMENT 不回滚
【发布时间】:2021-11-07 03:49:33
【问题描述】:

我正在使用 MySQL 的 AUTO_INCREMENT 字段和 InnoDB 来支持事务。我注意到当我回滚事务时,AUTO_INCREMENT 字段没有回滚?我发现它是这样设计的,但是有什么解决方法吗?

【问题讨论】:

  • 请注意:但 auto_increment 值将在服务器重置后重置为列的 max+1。
  • 它不是特定于 mysql 的,Postgres 的行为方式相同。解释是有道理的。

标签: mysql transactions innodb auto-increment


【解决方案1】:

它不能那样工作。考虑:

  • 程序一,你打开一个事务并插入到一个表 FOO 中,它有一个 autoinc 主键(我们随意说它的键值是 557)。
  • 程序二启动,它打开一个事务并插入表 FOO 得到 558。
  • 将两个插入编程到表 BAR 中,该表有一个列是 FOO 的外键。所以现在 558 位于 FOO 和 BAR 中。
  • 程序二现在提交。
  • 程序三启动并从表 FOO 生成报告。打印 558 记录。
  • 之后,程序一回滚。

数据库如何回收 557 值?它是否进入 FOO 并减少所有其他大于 557 的主键?它如何修复 BAR?报告程序三输出上打印的558如何擦除?

出于同样的原因,Oracle 的序列号也独立于事务。

如果你能在恒定时间内解决这个问题,我相信你可以在数据库领域赚到很多钱。

现在,如果您要求您的自动增量字段永远不会有间隙(例如,出于审计目的)。然后你不能回滚你的交易。相反,您需要在记录中添加状态标志。在第一次插入时,记录的状态为“未完成”,然后您开始交易,完成您的工作并将状态更新为“竞争”(或任何您需要的)。然后,当您提交时,记录就会生效。如果事务回滚,不完整的记录仍然存在以供审计。这会给您带来许多其他麻烦,但这是处理审计跟踪的一种方法。

【讨论】:

    【解决方案2】:

    让我指出一些非常重要的事情:

    您永远不应该依赖自动生成的键的数字特征。

    也就是说,除了比较它们是否相等(=)或不相等(),你不应该做任何其他事情。没有关系运算符(),没有按索引排序等。如果您需要按“添加日期”排序,请有一个“添加日期”列。

    将它们视为苹果和橙子:问苹果是否与橙子相同是否有意义?是的。问一个苹果是否比一个橙子大有意义吗?不。(实际上,确实如此,但你明白我的意思。)

    如果你坚持这条规则,自动生成的索引连续性的差距不会造成问题。

    【讨论】:

    • 你能稍微限定一下吗?我的意思是,当 autoinc 列中已经有一个非常好的价值可以使用时,为什么还要添加一个完整的其他列以及所需的所有开销(索引管理、磁盘使用、更多 IO 等)?根据定义,它提供了一个永远不会重复的独特价值,并且随着记录的添加而不断增加。唯一不是连续的,但是只要您假设序列中有间隙,我认为使用它来执行诸如按插入顺序对一组记录进行排序之类的事情没有问题。事实上,就性能和清晰度而言,我认为这是最好的方式。
    • 这是一个建议,不是答案
    • 添加日期是在提交时还是插入时发生。由于执行时间的延迟,如果多个线程插入表中,是否会在较早的时间戳之前看到较晚的时间戳?
    • 做了很多年的开发者,看了这个回答,感觉,豁然开朗!
    • @JoaquínL.Robles 的建议太好了,以至于我最终忘记了答案
    【解决方案3】:

    我有一个客户需要该 ID 以在发票表上回滚,其中订单必须是连续的

    我在 MySQL 中的解决方案是删除 AUTO-INCREMENT 并从表中提取最新的 Id,添加一个 (+1),然后手动插入。

    如果表名为“TableA”且自增列为“Id”

    INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
    VALUES (
    (SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
    Col2_Val, Col3_Val, Col4_Val, ...)
    

    【讨论】:

    • 或许这样会更好(SELECT MAX(id) AS maxval FROM table_name;)+1
    • 这不会是原子操作,当多个查询获得相同的maxID时肯定会有小窗口。
    • @P.Prasad - 你不能使用 LOCK TABLES 来防止这种情况吗?
    • 这应该放在答案中。
    • 您可以使用 LOCK TABLES,但这意味着一次只有一个事务可以访问您的表。在许多应用程序中,有数十个或数百个并发会话插入或更新表中的行。在您提交事务之前,自动增量不会持有锁定,它只会短暂锁定,足够长以增加 id。 (这是一个旧答案,但它今天才出现在我的提要中)
    【解决方案4】:

    你为什么关心它是否被回滚? AUTO_INCREMENT 键字段不应该有任何意义,所以你真的不应该关心使用什么值。

    如果您有想要保留的信息,则可能需要另一个非关键列。

    【讨论】:

      【解决方案5】:

      我不知道有什么方法可以做到这一点。根据MySQL Documentation,这是预期的行为,所有 innodb_autoinc_lock_mode 锁定模式都会发生。具体文字为:

      在所有锁定模式(0、1 和 2)中,如果 产生的交易 自动增量值回滚, 这些自动增量值是 “丢失。”一旦为 一个自增列,它不能是 回滚,无论是否 “INSERT-like”语句完成, 以及是否包含 事务被回滚。如此失落 值不被重用。因此,可能有 是存储在一个值的差距 表的 AUTO_INCREMENT 列。

      【讨论】:

        【解决方案6】:

        如果在回滚或删除后将auto_increment 设置为1,则在下一次插入时,MySQL 将看到1 已被使用,而是获取MAX() 值并将其加1。

        这将确保如果具有最后一个值的行被删除(或插入回滚),它将被重用。

        要将 auto_increment 设置为 1,请执行以下操作:

        ALTER TABLE tbl auto_increment = 1
        

        这不如简单地继续下一个数字有效,因为MAX() 可能很昂贵,但如果您不经常删除/回滚并且痴迷于重用最高值,那么这是一种现实的方法。

        请注意,这并不能防止中间删除记录的间隙,或者在您将 auto_increment 设置回 1 之前是否应该发生另一个插入。

        【讨论】:

        • 另外注意,如果你这样做,你应该在之后调用ANALYZE TABLE tbl,否则旧的AUTO_INCREMENT值仍然会出现在information_schema.TABLES中直到它过期。
        【解决方案7】:
        INSERT INTO prueba(id) 
        VALUES (
        (SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
        

        如果表不包含值或零行

        为 SELECT 上的错误 mysql 类型更新添加目标

        【讨论】:

          【解决方案8】:

          对这个特定困境(我也有)的具体答案如下:

          1) 创建一个表格,为不同的文件(发票、收据、RMA 等)保存不同的计数器;为每个文档插入一条记录,并将初始计数器添加到 0。

          2) 在创建新文档之前,请执行以下操作(例如发票):

          UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'
          

          3) 获取您刚刚更新到的最后一个值,如下所示:

          SELECT LAST_INSERT_ID()
          

          或者只是使用您的 PHP(或其他)mysql_insert_id() 函数来获得相同的结果

          4) 插入新记录以及刚从数据库中返回的主 ID。这将覆盖当前的自动增量索引,并确保您的记录之间没有 ID 间隔。

          当然,整个事情都需要包装在一个事务中。这种方法的美妙之处在于,当您回滚事务时,您在第 2 步中的 UPDATE 语句将被回滚,并且计数器将不再更改。其他并发事务将阻塞,直到第一个事务被提交或回滚,因此它们将无法访问旧计数器或新计数器,直到所有其他事务首先完成。

          【讨论】:

            【解决方案9】:

            解决方案:

            我们以'tbl_test'为例,假设'Id'字段有AUTO_INCREMENT属性

            CREATE TABLE tbl_test (
            Id int NOT NULL AUTO_INCREMENT ,
            Name varchar(255) NULL ,
            PRIMARY KEY (`Id`)
            )
            ;
            

            假设该表已经插入了一百或一千行,并且您不想再使用 AUTO_INCREMENT;因为当您回滚事务时,“Id”字段总是将 +1 添加到 AUTO_INCREMENT 值。 因此,为避免这种情况,您可能会这样做:

            • 让我们从“Id”列中删除 AUTO_INCREMENT 值(这不会删除您插入的行):
            ALTER TABLE tbl_test MODIFY COLUMN Id int(11) NOT NULL FIRST;
            • 最后,我们创建一个 BEFORE INSERT 触发器来自动生成一个“Id”值。但是即使你回滚任何事务,使用这种方式也不会影响你的 Id 值。
            创建触发器 trg_tbl_test_1
            在插入 tbl_test 之前
            每一行
              开始
                SET NEW.Id= COALESCE((SELECT MAX(Id) FROM tbl_test),0) + 1;
              结束;

            就是这样!大功告成!

            不客气。

            【讨论】:

            【解决方案10】:

            如果您需要按数字顺序分配 id 且没有间隔,则不能使用自动增量列。您需要定义一个标准整数列并使用一个存储过程来计算插入序列中的下一个数字并将记录插入事务中。如果插入失败,则下次调用该过程时,它将重新计算下一个 id。

            话虽如此,但依赖 id 以某种特定顺序排列而没有间隙是一个坏主意。如果您需要保留顺序,您可能应该在插入时(也可能在更新时)为该行添加时间戳。

            【讨论】:

            • 这意味着一次访问同一个表的两个事务永远无法同时成功提交。这可能是一个巨大的瓶颈。如果你有很多外键,当它们有多对多的关系时,你很容易陷入死锁。我对间隙处理的回答只是有点痛。 :-)
            【解决方案11】:
            $masterConn = mysql_connect("localhost", "root", '');
            mysql_select_db("sample", $masterConn);
            
            for($i=1; $i<=10; $i++) {
                mysql_query("START TRANSACTION",$masterConn);
                $qry_insert = "INSERT INTO `customer` (id, `a`, `b`) VALUES (NULL, '$i', 'a')";
                mysql_query($qry_insert,$masterConn);
                if($i%2==1) mysql_query("COMMIT",$masterConn);
                else mysql_query("ROLLBACK",$masterConn);
            
                mysql_query("ALTER TABLE customer auto_increment = 1",$masterConn);
            
            }
            
            
            
            echo "Done";
            

            【讨论】:

            • 不知道,为什么这被淘汰了...这解决了问题
            • 感谢 Martin Zvarík 的评论
            猜你喜欢
            • 2011-06-12
            • 2016-07-26
            • 2014-11-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多