检查这段简单的代码,它使用生成器在 Firebird 表中创建唯一的主键:
CREATE OR ALTER TRIGGER ON_BEFOREINSERT_PK_BOOKING_ITEM FOR BOOKING_ITEM BEFORE INSERT POSITION 0
AS
BEGIN
IF ((NEW.booking_item_id IS NULL) OR (NEW.booking_item_id = 0)) THEN BEGIN
SELECT GEN_ID(LastIdBookingItem, 1) FROM RDB$DATABASE INTO :NEW.booking_item_id;
END
END!
该触发器抓取并递增,然后为预订项目 id 分配生成的值,从而为 BOOKING_ITEM 表创建一个自动递增的键。触发器甚至会检查预订 ID 是否尚未分配值。
问题是,如果由于某种原因无法发布 BOOKING_ITEM 记录,自动递增的值将会丢失(浪费)。
关于如何避免这种浪费,我有几个想法,但对每一个都感到担忧。他们来了:
如果发生过帐错误则递减计数器。在触发器中,我设置了一个 try- except 块(Firebird PSQL 中是否存在 try- except 块?)并运行SELECT GEN_ID(LastIdBookingItem, -1) FROM RDB$DATABASE
关于帖子异常。这行得通吗?如果另一笔交易潜入并在我递减生成器之前递增生成器怎么办?那真的会把事情搞砸的。
使用临时 ID。将 id 设置为某个唯一的临时值,我将其更改为插入后触发器上所需的生成器值。这种方法感觉有点做作,需要一种确保临时 ID 唯一的方法。但是,如果 booking_item_id 是在客户端提供的,我如何将其与临时 id 区分开来呢?另外我需要另一个触发器
使用事务控制。这类似于选项 1。除了不使用 try-except 块来重置生成器之外,我启动一个事务,然后在记录无法发布时回滚它。我不知道使用事务控制的语法。我想我在某处读到 PSQL 中不允许 SAVEPOINT/SET TRANSACTION 。另外,回滚必须在 AFTER INSERT 触发器中发生,所以我再次需要另一个触发器。
对于任何想要使用生成器的 Firebird 开发人员来说,这无疑是一个问题。还有其他想法吗?我有什么遗漏的吗?
序列不受事务控制,干预它们以获得“无间隙”数字只会引起麻烦,因为另一个事务也可以同时增加序列,导致间隙+重复而不是没有间隙:
- 开始:生成器值 = 1
- T1:增量:值为2
- T2:增量:值为3
- T1:“回滚”,递减:值为 2(而不是您期望的 1)
- T3:增量:值为 3 => 重复值
序列主要用于生成人工主键,并且您不应该关心间隙的存在:只要数字唯一标识记录即可。
如果您需要可审计的数字序列,并且要求没有间隙,那么您不应该使用数据库序列来生成它。您可以在创建并提交发票本身后使用序列来分配编号(以便确保它被持久化)。没有编号的发票还不是最终的。然而,即使在这里,也存在出现间隙的机会,例如,如果在分配发票编号和提交之间发生错误或其他失败。
另一种方法可能是明确创建带有间隙编号的零发票(标记为已取消/编号丢失),以便审核员知道该发票发生了什么。
根据当地法律和法规,您不应“重复使用”或回收丢失的号码,因为这可能会被视为欺诈。
您可能会发现其他想法“一系列可审计的数字”。其中还包含一个使用 IBObjects 的 Delphi 项目,但文档本身很好地描述了问题和可能的解决方案。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)