79

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

4

11 回答 11

100

它不能那样工作。考虑:

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

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

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

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

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

于 2009-01-16T03:11:10.493 回答
76

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

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

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

把它们当作苹果和橘子:问苹果是否和橘子一样有意义吗?是的。问一个苹果是否比一个橙子大有意义吗?不。(实际上,确实如此,但你明白我的意思。)

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

于 2009-01-16T03:12:34.657 回答
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, ...)
于 2011-06-09T15:07:12.163 回答
11

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

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

于 2009-01-16T02:55:29.760 回答
9

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

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

于 2009-01-16T02:58:21.473 回答
8

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

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

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

ALTER TABLE tbl auto_increment = 1

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

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

于 2012-02-16T14:14:22.213 回答
3
INSERT INTO prueba(id) 
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))

如果表不包含值或零行

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

于 2011-11-10T23:29:00.037 回答
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 语句将被回滚,并且计数器将不再更改。其他并发事务将阻塞,直到第一个事务被提交或回滚,因此它们将无法访问旧计数器或新计数器,直到所有其他事务首先完成。

于 2014-09-01T15:48:42.180 回答
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”字段总是将 +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;
  结尾;

就是这样!你完成了!

别客气。
于 2015-11-18T21:45:01.773 回答
0

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

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

于 2009-01-16T03:08:43.150 回答
0
$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";
于 2018-07-06T09:16:18.570 回答