MySQL AUTO_INCREMENT没有ROLLBACK

时间:2009-01-16 02:49:44

标签: mysql transactions innodb auto-increment

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

11 个答案:

答案 0 :(得分:73)

它无法正常工作。考虑:

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

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

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

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

现在,如果您要求自动增量字段永远不会有间隙(例如,出于审计目的)。然后你无法回滚你的交易。相反,您需要在记录上有一个状态标志。在第一次插入时,记录的状态为“未完成”,然后您开始交易,完成工作并将状态更新为“竞争”(或您需要的任何内容)。然后当你提交时,记录是实时的。如果事务回滚,则不完整的记录仍然存在于审计中。这会引起许多其他问题,但这是处理审计跟踪的一种方法。

答案 1 :(得分:53)

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

您永远不应该依赖自动生成密钥的数字功能。

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

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

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

答案 2 :(得分:12)

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

答案 3 :(得分:11)

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

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

答案 4 :(得分:8)

我不知道有什么方法可以做到这一点。根据{{​​3}},这是预期的行为,将在所有 innodb_autoinc_lock_mode 锁定模式下发生。具体文字是:

  

在所有锁定模式(0,1和2)中,如果a   生成的事务   自动递增值回滚,   那些自动增量值是   “丢失。”生成一个值后   一个自动增量列,它不能   回滚,无论是否   “INSERT-like”声明完成,   以及是否含有   交易回滚。这样输了   值不会被重用。因此,有可能   是存储在一个值中的差距   表的AUTO_INCREMENT列。

答案 5 :(得分:4)

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

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

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

ALTER TABLE tbl auto_increment = 1

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

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

答案 6 :(得分:2)

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

如果表格不包含值或零行

在SELECT

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

答案 7 :(得分:1)

解决方案:

让我们使用'tbl_test'作为示例表,并假设字段'Id'具有AUTO_INCREMENT属性

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

假设该表已经插入了houndred或千行,并且您不想再使用AUTO_INCREMENT了;因为当您回滚事务时,字段'Id'始终向AUTO_INCREMENT值添加+1。 所以为了避免这种情况你可能会这样做:

  • 让我们从列'Id'中删除AUTO_INCREMENT值(这不会删除插入的行):
ALTER TABLE tbl_test MODIFY COLUMN Id  int(11) NOT NULL FIRST;
  • 最后,我们创建一个BEFORE INSERT Trigger来自动生成'Id'值。但即使您回滚任何事务,使用这种方式也不会影响您的Id值。
CREATE TRIGGER trg_tbl_test_1
BEFORE INSERT ON tbl_test
FOR EACH ROW
  BEGIN
    SET NEW.Id= COALESCE((SELECT MAX(Id) FROM tbl_test),0) + 1;
  END;

就是这样!你做完了!

欢迎你。

答案 8 :(得分:0)

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

话虽如此,依赖于某些特定顺序的ID并没有差距是一个坏主意。如果您需要保留排序,则可能应该在插入时加上行(并且可能在更新时)。

答案 9 :(得分:0)

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

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)将您的新记录与您刚刚从DB获取的主ID一起插入。这将覆盖当前的自动增量索引,并确保您的记录之间没有ID间隔。

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

答案 10 :(得分:-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";