postgresql 生成没有间隙的序列

Posted

技术标签:

【中文标题】postgresql 生成没有间隙的序列【英文标题】:postgresql generate sequence with no gap 【发布时间】:2019-11-17 08:56:30 【问题描述】:

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

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

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

似乎唯一的解决方案是放一些重试代码。

【问题讨论】:

无间隙序列有复杂的解决方案。但是,如果您希望它简单且正确,请在 try 块内执行此操作,检查主键违规异常并重试。 My answer here. 另见***.com/questions/9984196/… Gaps between primary key id in sql table的可能重复 【参考方案1】:

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

【讨论】:

【参考方案2】:

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

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

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

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

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

    数字系列不得有空格。 多个进程创建与编号关联的实体(例如发票)。 必须在创建实体时生成数字。

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

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

如果您的数字系列可能有间隙(并且您有多个需要即时生成数字的进程),则使用 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),并使用唯一键约束保护发票#,并可能检查缺失值如果你真的非常关心,单独的代码。

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

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

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

Original article

【讨论】:

好吧,我看不出这对我有什么帮助,我有 postgresql 并且我看到了事务开始的数据。 Oralce 和 PostgreSQL 具有相同的读取一致性模型,因此对数字生成器的访问锁定原则也适用。 好的,那我需要什么类型的锁?这如何在序列化隔离级别上起作用?【参考方案3】:

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

    并行进程回滚 锁定超时

你会明白为什么。

【讨论】:

【参考方案4】:

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

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

更多信息here

【讨论】:

由于其他人指出的原因,这不能保证无间隙序列:最明显的是,两个事务可以声明 ID,但一个被回滚,因此没有使用 ID,留下一个间隙在插入的系列中。 虽然回滚可能会产生间隙是对的,但可以通过获取序列 ID 并在正常情况下不会回滚的事务期间插入它来避免这种情况。例如创建发票,我会插入发票记录,进行所有库存和会计修改,然后请求发票编号,并使用发票 ID 更新发票记录(发票表有一个单独的内部 PK)。跨度> @Andres Olarte - 这是个好主意!似乎最容易实现 @AndresOlarte 是的,您当然可以分配号码,然后在批处理作业中进行。如果你采用这种方法,那么使用序列就没有任何价值——你可以读取表中当前的最高值,然后生成后续值。

以上是关于postgresql 生成没有间隙的序列的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql 序列生成器如何工作?

MySQL建表语句转PostgreSQL建表语句全纪录

序列中的最大出现次数(高级间隙和孤岛问题)

PostgreSQL 错误的所有权

在PostgreSQL 和 Hive中生成日期序列

Python pandas 绘制带间隙的时间序列