8

我必须/必须为发票创建唯一 ID。我有一个表 ID 和这个唯一编号的另一列。我使用序列化隔离级别。使用

  var seq = @"SELECT invoice_serial + 1 FROM  invoice WHERE ""type""=@type ORDER BY invoice_serial DESC LIMIT 1";

没有帮助,因为即使使用 FOR UPDATE 它也不会像序列化级别那样读取正确的值。

只有解决方案似乎放了一些重试代码。

4

4 回答 4

24

序列不会生成无间隙的数字集,并且实际上没有办法让它们这样做,因为回滚或错误将“使用”序列号。

不久前我写了一篇关于这个的文章。它是针对 Oracle 的,但实际上是关于无间隙数字的基本原则,我认为这同样适用于这里。

嗯,又发生了。有人如何实现生成无间隙数字序列的要求,而一群反对者纷纷向他们说(这里我稍微解释一下)这会降低系统性能,这很少是一个有效的要求,写要求的人是白痴等等。

正如我在线程中指出的那样,生成无间隙的数字序列有时是真正的法律要求。英国超过 2,000,000 个已注册增值税(销售税)的组织的发票号码有这样的要求,其原因相当明显:这使得向税务机关隐藏收入的产生变得更加困难。我看到评论说这是西班牙和葡萄牙的要求,如果在许多其他国家/地区没有要求,我不会感到惊讶。

那么,如果我们接受这是一个有效的要求,那么在什么情况下数字的无间隙序列*会成为问题?集体思考通常会让你相信它总是如此,但实际上它只是在非常特殊的情况下的一个潜在问题。

  1. 这一系列数字必须没有间隙。
  2. 多个进程创建与编号相关联的实体(例如发票)。
  3. 编号必须在创建实体时生成。

如果必须满足所有这些要求,那么您的应用程序中就有一个序列化点,我们稍后会讨论这个问题。

首先,让我们谈谈实现数字序列要求的方法,如果您可以放弃这些要求中的任何一个。

如果您的数字系列可能有间隙(并且您有多个需要即时生成数字的进程),则使用 Oracle 序列对象。它们具有非常高的性能,并且已经很好地讨论了可以预期存在差距的情况。如果这很重要,那么通过设计努力来最小化在生成数字和提交事务之间的过程失败的可能性来最小化跳过的数字数量并不是太具有挑战性。

如果您没有创建实体的多个流程(并且您需要一个必须立即生成的无间隙数字系列),就像批量生成发票的情况一样,那么您已经有了一个序列化点。这本身可能不是问题,并且可能是执行所需操作的有效方式。在这种情况下,生成无间隙数字相当简单。您可以通过多种技术读取当前最大值并将递增值应用于每个实体。例如,如果您要从临时工作表中将一批新发票插入发票表中,您可能会:

insert into
  invoices
    (
    invoice#,
    ...)
with curr as (
  select Coalesce(Max(invoice#)) max_invoice#
  from   invoices)
select
  curr.max_invoice#+rownum,
  ...
from
  tmp_invoice
  ...

当然,您会保护您的流程,以便一次只能运行一个实例(如果您使用的是 Oracle,可能使用 DBMS_Lock),并使用唯一键约束保护发票#,并且可能使用单独的代码检查缺失值,如果你真的,真的很在乎。

如果您不需要即时生成数字(但您需要它们无间隙并且多个进程生成实体),那么您可以允许生成实体并提交事务,然后将数字的生成留给单个批次工作。实体表的更新,或插入到单独的表中。

那么,如果我们需要通过多个进程即时生成无间隙数字系列的三重奏呢?我们所能做的就是尽量减少过程中的序列化时间,我提供以下建议,并欢迎任何额外的建议(当然也可以是反建议)。

  1. 将当前值存储在专用表中。不要使用序列。
  2. 通过将其封装在函数或过程中,确保所有进程使用相同的代码来生成新数字。
  3. 使用 DBMS_Lock 序列化对数字生成器的访问,确保每个系列都有自己的专用锁。
  4. 保持序列生成器中的锁,直到您的实体创建事务通过在提交时释放锁来完成
  5. 将数字的生成延迟到最后可能的时刻。
  6. 考虑在生成数字之后和提交完成之前发生意外错误的影响——应用程序会优雅地回滚并释放锁,还是会保持序列生成器上的锁直到会话稍后断开连接?无论使用哪种方法,如果交易失败,则必须将序列号“返回到池中”。
  7. 你能把整个事情封装在实体表上的触发器中吗?您可以将其封装在插入行并自动提交插入的表或其他 API 调用中吗?

来源文章

于 2013-09-25T13:29:28.957 回答
2

您可以创建一个没有缓存的序列,然后从序列中获取下一个值并将其用作您的计数器。

CREATE SEQUENCE invoice_serial_seq START 101 CACHE 1;
SELECT nextval('invoice_serial_seq');

更多信息在这里

于 2013-09-25T13:10:19.780 回答
2

您可以将表锁定为插入,和/或需要重试代码。没有其他选择。如果你停下来想想会发生什么:

  1. 并行进程回滚
  2. 锁定超时

你会明白为什么。

于 2013-09-25T13:28:28.263 回答
0

2006 年,有人在 PostgreSQL 邮件列表中发布了一个 gapless-sequence 解决方案:http ://www.postgresql.org/message-id/44E376F6.7010802@seaworthysys.com

于 2015-07-22T13:52:22.867 回答