MySQL AUTO_INCRMENT 不回滚
-
19-08-2019 - |
题
我使用 MySQL 的 AUTO_INCRMENT 字段和 InnoDB 来支持事务。我注意到当我回滚事务时,AUTO_INCRMENT 字段没有回滚?我发现它是这样设计的,但是有什么解决方法吗?
解决方案
我要指出很重要的事:
你永远不应该依赖于自动生成密钥的数字功能。
也就是说,不是比较它们是否相等(=)或不相等其他(<>),你不应该做任何事情。没有关系运算符(<,>),不排序的索引,等等。如果你需要通过“添加日期”进行排序,有一个“添加日期”一栏。
把它们作为苹果和桔子:是否有意义问,如果一个苹果是一样的橘子?是。是否有意义问,如果一个苹果比橙色大?号(事实上,确实如此,但你明白我的意思。)
如果你坚持这个规则,在自动生成指数的连续性差距不会产生问题。
其他提示
那样是行不通的。考虑:
- 程序一,您打开一个事务并插入到具有 autoinc 主键的表 FOO 中(任意地,我们说它的键值是 557)。
- 程序二启动,它打开一个事务并插入到表 FOO 中,得到 558。
- 将两个插入编程到表 BAR 中,该表有一列是 FOO 的外键。所以现在 558 位于 FOO 和 BAR 中。
- 程序二现在提交。
- 程序三启动并从表 FOO 生成报告。558 记录被打印。
- 之后,程序一回滚。
数据库如何回收557值?它是否会进入 FOO 并递减所有其他大于 557 的主键?它如何修复 BAR?它如何擦除打印在报告程序三个输出上的558?
出于同样的原因,Oracle 的序列号也独立于事务。
如果你能在恒定的时间内解决这个问题,我相信你可以在数据库领域赚很多钱。
现在,如果您要求自动增量字段永远不会有间隙(例如,出于审计目的)。那么您就无法回滚您的交易。相反,您需要在记录上有一个状态标志。第一次插入时,记录的状态为“不完整”,然后您开始事务,完成工作并将状态更新为“竞争”(或您需要的任何内容)。然后,当您提交时,记录就会生效。如果事务回滚,不完整的记录仍然存在以供审计。这会给您带来许多其他麻烦,但这是处理审计跟踪的一种方法。
我有一个客户所需要的ID回滚发票的表,其中的顺序必须是连续的
我在MySQL溶液以除去自动递增和从表中拉出的最新标识,添加一个(+1),然后手动将其插入。
如果该表被命名为“表A”和自动递增列是“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重点领域不应该有任何意义,所以你真的不应该关心用什么样的价值。
如果你有信息,你想保留,或许需要另一个非键列。
我不知道有什么办法做到这一点。据href="http://dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html" rel="noreferrer"> MySQL的文档的 innodb_autoinc_lock_mode 的锁模式发生。特定文本是:
在所有的锁模式(0,1和2)中,如果 生成交易 自动递增值回滚, 那些自动递增值 “丢失”。一旦一个值被用于生成 自动增量列,就不能 回滚,是否不 “INSERT样”语句完成, 和是否含 事务回滚。这种失落 值不被重用。因此,有可能 可以在这些值的差距存储在 表的AUTO_INCREMENT列。
如果您设置auto_increment
回滚或删除后1
,在接下来的插入,MySQL将看到1
已被使用而会得到MAX()
价值,把它加1。
这将确保,如果与上次值的行被删除(或插入被回滚),它将被重新使用。
要的AUTO_INCREMENT设置为1,这样做:
ALTER TABLE tbl auto_increment = 1
这是不是有效,因为简单地继续与下一个号码,因为MAX()
可以是昂贵的,但如果你删除/回滚很少与再利用价值最高的痴迷,那么这是一个现实的方法。
请注意,这并不妨碍从中间或者删除的记录空白另一插入之前你设定AUTO_INCREMENT回到1应该发生。
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
如果表中不包含的值或零行
上选择添加的目标为错误的MySQL类型更新FROM
解决方案:
让我们使用“tbl_test”作为示例表,并假设字段“Id”具有 AUTO_INCRMENT 属性
CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;
假设该表已插入数百行或数千行,并且您不想再使用 AUTO_INCRMENT;因为当您回滚事务时,字段“Id”始终会向 AUTO_INCRMENT 值添加+1。因此,为了避免这种情况,您可以这样做:
- 让我们从“Id”列中删除 AUTO_INCREMENT 值(这不会删除插入的行):
ALTER TABLE tbl_test MODIFY COLUMN Id int(11) NOT NULL FIRST;
- 最后,我们创建一个 BEFORE INSERT 触发器来自动生成“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;
就是这样!你完成了!
不客气。
如果您需要指定按数字顺序而没有间隙的ID,则可以不使用自动增量列。你需要定义一个标准的整数列,并使用该计算在插入序列的下一个数字,并插入一个事务中记录的存储过程。如果插入失败,则下一次的程序被调用时将重新计算的下一个ID。
说了这么多,这是一个坏主意,依靠IDS中,没有空位一些特定顺序。如果您需要保留排序,你应该对时间戳插入行(和潜在的更新)。
混凝土回答这个特定困境(我也有)如下:
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,你刚刚从DB回来一起。这将覆盖当前汽车增量指标,并确保你有你的记录之间没有ID的差距。
此整个事情需要一个事务中被包装,当然。这种方法的好处在于,当你回滚事务,从第2步您的UPDATE语句将被回滚,计数器不会改变了。其他并发事务将被阻塞,直到第一个事务提交或回滚所以他们不会有机会获得老任计数器或一个新的,直到所有其他事务首先完成了。
$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";