MySQL AUTO_INCREMENT不会回滚


71

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


2
请注意:服务器重置后,auto_increment值将重置为该列的max + 1。
JD Fitz.Gerald 09年

1
它不是特定于mysql的,Postgres的行为方式相同。这些解释是有道理的。
尼尔斯2012年

Answers:


71

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

您永远不要依赖自动生成键的数字功能。

也就是说,除了将它们的相等性(=)或不相等性(<>)进行比较之外,您不应做任何其他事情。没有关系运算符(<,>),没有按索引排序,等等。如果需要按“添加日期”排序,请在“添加日期”列中添加。

将它们当作苹果和橙子:问一个苹果是否和橙子一样有意义吗?是。询问苹果是否大于橙子是否有意义?不。(实际上,确实可以,但是您明白我的意思。)

如果您遵守此规则,则自动生成索引的连续性差异不会引起问题。


7
你能胜任这个吗?我的意思是,为什么要在autoinc列中已经有非常好的价值使用时,为什么还要增加一整列,以及随之而来的所有开销(索引管理,磁盘使用,更多IO等)?根据定义,它提供了一个唯一的值,该值永远不会重复,并且会随着记录的添加而不断增加。唯一的事情不是连续的,但是只要您假设序列中有间隔,我就不会发现使用它执行诸如按插入顺序对一组记录进行排序之类的操作没有问题。实际上,就性能和清晰度而言,我想这是最好的方法。
先生 w

44
这是一个建议,不是一个答案
华金属罗伯斯

添加日期的添加是在提交时还是在插入时发生。如果多个线程插入表中,是否会因为执行时间的延迟而在较早的时间戳之前显示较晚的时间戳,这会发生吗?
user2505915 '16

作为开发人员很多年,在阅读了这个答案之后,我感到自己很开明!
evilReiko

@JoaquínL.Robles的建议是如此之好,以至于我最终忘记了答案
peterchaula

92

它不能那样工作。考虑:

  • 在程序1中,您打开一个事务并将其插入具有自动增量主键的表FOO中(任意地,我们说它的键值得到557)。
  • 程序二开始,它打开一个事务并插入到表FOO中,得到558。
  • 将两个插入程序编程到表BAR中,该表的一列是FOO的外键。因此,现在558位于FOO和BAR中。
  • 程序2现在提交。
  • 程序3启动并从表FOO生成报告。558记录被打印。
  • 之后,程序一回滚。

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

出于相同的原因,Oracle的序列号也与事务无关。

如果您可以在固定时间内解决此问题,那么我相信您可以在数据库领域赚很多钱。

现在,如果您有一个要求,您的自动递增字段必须没有空格(例如,出于审计目的)。这样便无法回滚您的交易。相反,您需要在记录上有一个状态标志。第一次插入时,记录的状态为“未完成”,然后开始事务,进行工作并将状态更新为“竞争”(或任何需要的状态)。然后,当您提交时,记录处于活动状态。如果事务回滚,则不完整的记录仍然存在以供审核。这将引起您很多其他麻烦,但这是处理审计跟踪的一种方法。


14

我有一个客户需要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, ...)

4
也许这样会更好(SELECT MAX(id) AS maxval FROM table_name;)+1
vinsa 2014年

13
这将不是原子操作,当多个查询获得相同的maxID时,肯定会有一个小窗口。
Ouroboros

1
@ P.Prasad-您不能使用LOCK TABLES来防止这种情况吗?
Michael Corrigan

2
那应该放在答案中。
Ouroboros

1
您可以使用LOCK TABLES,但这意味着一次只能有一个事务可以访问您的表。在许多应用程序中,有数十个或数百个并发会话在表中插入或更新行。在您提交事务之前,自动递增不会持有锁定,它只会短暂锁定,足以使id递增。(这是一个旧答案,但今天它在我的供稿中突然弹出)
Bill Karwin

11

您为什么关心它是否回滚?AUTO_INCREMENT键字段不应具有任何含义,因此您实际上不必关心使用了什么值。

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


9

我不知道有什么办法。根据MySQL文档,这是预期的行为,将在所有innodb_autoinc_lock_mode锁定模式下发生。具体文本为:

在所有锁定模式(0、1和2)中,如果生成自动增量值的事务回滚,则这些自动增量值将“丢失”。一旦为自动增量列生成了一个值,就无法回滚该值,无论“ INSERT-like”语句是否完成以及包含的事务是否回滚。这种丢失的值不会重复使用。因此,在表的AUTO_INCREMENT列中存储的值中可能存在间隙。


8

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

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

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

ALTER TABLE tbl auto_increment = 1

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

请注意,这不能防止中间删除的记录出现间隙,或者在将auto_increment设置回1之前是否应该进行另一次插入。


1
还要注意,如果执行此操作,则应ANALYZE TABLE tbl在之后调用,否则旧的AUTO_INCREMENT值仍将显示在information_schema.TABLES中,直到过期。
hackel

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

如果表不包含值或零行

添加目标错误的mysql类型更新从选择


1

针对这个特定难题(我也有)的具体答案如下:

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语句将被回滚,并且计数器将不再更改。其他并发事务将阻塞,直到第一个事务被提交或回滚,这样它们将无法访问旧计数器或新计数器,直到所有其他事务都首先完成。


1

解:

让我们以“ 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修改列ID int(11)不为空;
  • 最后,我们创建一个BEFORE INSERT触发器以自动生成一个“ Id”值。但是,即使回滚任何事务,使用这种方式也不会影响您的ID值。
创建触发器trg_tbl_test_1
在tbl_test上插入之前
每行
  开始
    SET NEW.Id = COALESCE((从tbl_test中选择MAX(Id)),0)+ 1;
  结束;

而已!你完成了!

别客气。

我喜欢您的解决方案,它对并发请求的表现如何?尤其是@Ouroboros在stackoverflow.com/questions/449346/…中描述的情况,它可以解决吗?
KumZ

@KumZ,这可能会帮助您解决问题stackoverflow.com/a/42683736/4564384
mld.oscar 19/09/14

0

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

话虽这么说,但要让id保持特定顺序且没有间隙是一个坏主意。如果需要保留顺序,则可能应在插入时(并可能在更新时)标记行的时间戳。


这意味着两个事务一次击中同一个表永远无法成功提交。这可能是一个巨大的瓶颈。而且,如果您有许多外键,那么当它们具有多对多关系时,您很容易陷入僵局。我对间隙处理的回答只会有点痛。:-)
jmucchiello

-1
$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";
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.