MySQL AUTO_INCREMENT不会ROLLBACK

我使用MySQL的AUTO_INCREMENT字段和InnoDB来支持事务。 我注意到,当我回滚事务,AUTO_INCREMENT字段不回滚? 我发现它是这样devise的,但有没有解决这个问题的方法?

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

你不应该依赖于自动生成的键的数字特征。

也就是说,除了比较他们的平等(=)或不平等(<>),你不应该做任何事情。 没有关系运算符(<,>),没有索引sorting等。如果您需要按“添加date”进行sorting,请添加“添加date”列。

把它们当作苹果和橘子:问一个苹果是否和橘子一样有意义? 是。 问一个苹果是否大于橙色是否有意义? 没有(其实,但是你明白我的观点)

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

它不能这样工作。 考虑:

  • 编程一个,你打开一个事务,并插入一个表有一个autoinc主键(任意,我们说它的键值为557)。
  • 程序二开始,它打开一个事务并将其插入到获得558的表FOO中。
  • 将两个插入程序插入表BAR,其中有一列是FOO的外键。 所以现在558位于FOO和BAR。
  • scheme二现在提交。
  • 计划三开始并从表FOO生成报告。 558logging被打印。
  • 之后,程序回滚。

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

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

如果你能够不断地解决这个问题,我相信你可以在数据库领域赚很多钱。

现在,如果你有一个要求,你的自动增量字段永远不会有差距(为了审计的目的,说)。 那么你不能回滚你的交易。 相反,你需要在你的logging上有一个状态标志。 在第一次插入,logging的状态是“不完整”,然后你开始交易,做你的工作,并更新状态“竞争”(或任何你需要的)。 那么当你提交时,logging是活的。 如果事务回滚,则不完整的logging仍然在审核中。 这会导致你许多其他的麻烦,但是处理审计跟踪的一种方法。

我有一个客户需要的ID回滚一张发票表,其中的顺序必须是连续的

我在MySQL中的解决scheme是删除自动增量,并从表中拉最新的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, ...) 

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

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

我不知道有什么办法做到这一点。 根据MySQL的文档 ,这是预期的行为,并会发生在所有innodb_autoinc_lock_modelocking模式。 具体的文字是:

在所有locking模式(0,1和2)中,如果生成自动递增值的事务回退,则这些自动递增值将“丢失”。一旦为自动递增列生成值,则不能回滚,“INSERT-like”语句是否完成,以及包含的事务是否回滚。 这些丢失的价值不会被重复使用。 因此,存储在表格的AUTO_INCREMENT列中的值可能存在间隙。

如果在回滚或删除之后将auto_increment设置为1 ,那么在下一个插入操作中,MySQL将会看到1已经被使用,并且将取得MAX()值并将1加1。

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

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

 ALTER TABLE tbl auto_increment = 1 

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

请注意,这不会阻止在中间删除logging的间隙,或者在将auto_increment设置回1之前应该发生另一个插入。

 INSERT INTO prueba(id) VALUES ( (SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target)) 

如果该表不包含值或零行

添加目标为错误mysqltypes更新从SELECT上

解:

我们使用'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”始终将AUTO_INCREMENT值加1。 所以为了避免你可能做到这一点:

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

而已! 你完成了!

别客气。

如果您需要按数字顺序分配ID,且无间隙,则不能使用自动增量列。 您需要定义一个标准整数列,并使用存储过程来计算插入序列中的下一个数字,并将该logging插入到事务中。 如果插入失败,那么在下次调用该过程时将重新计算下一个ID。

说了这样的话,依靠id以某种特定的顺序没有差距是一个坏主意。 如果你需要保持sorting,你可能应该插入时间戳(可能在更新)。

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

1)创build一个表,其中包含不同文件(发票,收据,RMA等)的不同计数器。 为每个文档插入logging,并将初始计数器添加到0。

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

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

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

 SELECT LAST_INSERT_ID() 

或者只是使用你的PHP(或其他)mysql_insert_id()函数来获取相同的东西

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

当然,这整个事情需要被包装在一个交易中。 这种方法的好处在于,当你回滚一个事务时,你的第2步中的UPDATE语句将被回滚,并且计数器不会再改变。 其他并发事务会阻塞,直到第一个事务被提交或回滚,这样他们将不能访问旧的计数器或新的计数器,直到所有其他事务完成第一个。