我正在使用MySQL的AUTO_INCREMENT字段和InnoDB来支持事务。我注意到当我回滚事务时,AUTO_INCREMENT字段没有回滚?我发现这是设计上的问题,但有没有任何解决方法?
我正在使用MySQL的AUTO_INCREMENT字段和InnoDB来支持事务。我注意到当我回滚事务时,AUTO_INCREMENT字段没有回滚?我发现这是设计上的问题,但有没有任何解决方法?
这样是行不通的。请考虑以下情况:
数据库如何重新获取 557 的值?它会进入 FOO 并将大于 557 的所有其他主键减少吗?它如何修复 BAR?它如何消除程序三输出中的 558?
Oracle 的序列号也独立于事务,原因相同。
如果您有一个要求,即您的自动递增字段永远不会有间隙(例如出于审计目的)。那么您不能回滚事务。相反,您需要在记录上拥有状态标志。在第一次插入时,记录的状态为“未完成”,然后开始事务,进行工作并将状态更新为“完成”(或其他您需要的内容)。然后,在提交时,记录将生效。如果事务回滚,则未完成的记录仍然存在于表中进行审计。这将带来许多其他问题,但是这是处理审计跟踪的一种方法。
让我指出一些非常重要的事情:
也就是说,除了用等号(=)或不等号(<>)进行比较以外,您不应该做任何其他操作。不要使用关系运算符(<,>),不要通过索引排序等。如果您需要按“添加日期”排序,请设置一个“添加日期”列。
将它们视为苹果和橙子:询问苹果是否与橙子相同有意义吗?有。问一个苹果是否比橙子大有意义吗?不。 (实际上,它确实有意义,但是你明白我的意思。)
如果您遵循这个规则,自动生成的索引的连续性间隔不会导致问题。
我曾有一个客户需要在一张发票表中对ID进行回滚,要求订单必须是连续的。
我的MySQL解决方案是移除自增列并从表中获取最新的ID,加上1(+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
,意思是查询表中最大的 ID 值,并加上 1。 - vinsa你为什么在意它是否回滚?AUTO_INCREMENT主键字段本来就不应该有任何含义,所以你真的不应该关心使用了什么值。
如果你想保留信息,也许需要添加另一个非主键列。
auto_increment
设置为1
,则在下一次插入时,MySQL会发现1
已经被使用,而会获取MAX()
值并将其加1。
这将确保如果具有最后一个值的行被删除(或插入被回滚),它将被重用。
要将auto_increment设置为1,请执行以下操作:
ALTER TABLE tbl auto_increment = 1
这种做法不如简单地继续使用下一个数字高效,因为MAX()
可能很昂贵,但如果您很少删除/回滚并且痴迷于重复使用最高值,则这是一种可行的方法。
请注意,这不会防止在中间删除记录或在您将自动增量设置回1之前发生其他插入导致间隙。
ANALYZE TABLE tbl
,否则旧的 AUTO_INCREMENT 值将在 information_schema.TABLES 中仍然出现直到它过期。 - hackel我不知道有什么方法可以做到这一点。根据MySQL文档,无论是哪种innodb_autoinc_lock_mode锁定模式,都会出现这种情况。具体的文字如下:
在所有锁定模式(0、1和2)中,如果生成自增值的事务回滚,则这些自增值将“丢失”。一旦为自增列生成了值,无论“类似于INSERT”的语句是否完成,以及包含该事务是否回滚,都无法回滚该值。这些丢失的值不会被重用。因此,在表的AUTO_INCREMENT列中存储的值可能存在间隙。
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
解决方案:
让我们以“tbl_test”作为示例表,假设字段“Id”具有AUTO_INCREMENT属性。
CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;
如果您需要按数字顺序分配ID而没有间隙,那么您不能使用自动递增列。您需要定义一个标准整数列,并使用存储过程计算插入序列中的下一个数字并在事务内插入记录。如果插入失败,则下次调用该过程时将重新计算下一个ID。
话虽如此,依赖于ID以某种特定顺序且没有间隙是一个坏主意。如果您需要保留排序,您应该在插入时(可能还要更新)为行加上时间戳。
针对这个特定问题(我也遇到过),具体的解决方案如下:
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语句将被回滚,并且计数器不会再次更改。其他并发事务将被阻塞,直到第一笔交易被提交或回滚,因此在所有其他事务完成之前,它们将无法访问旧计数器或新计数器。