2013-09-25 28 views
5

我必须/必须为发票创建唯一ID。我有一个表ID和另一列这个唯一的号码。我使用序列化隔离级别。使用postgresql生成无间隙序列

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

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

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

+0

有一个无间隙序列复杂的解决方案。但是如果你希望它简单而正确的话,可以在'try'块中执行,检查主键违例异常并重试。 [我的答案在这里](http://stackoverflow.com/a/16465158/131874)。 –

+0

另请参阅http://stackoverflow.com/questions/9984196/rails-postgres-does-not-re-use-deleted-ids-but-mysql-does/ – IMSoP

+0

[sql表中主键id之间的差距可能重复](http://stackoverflow.com/questions/39099905/gaps-between-primary-key-id-in-sql-table) – e4c5

回答

1

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

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

更多信息here

+0

由于其他人指出的原因,这并不能保证无间隙序列:最明显的,两个事务可以声明ID,但是一个事务可以回滚,因此没有使用该ID,从而在插入的系列中留下空隙。 – IMSoP

+2

虽然你说得对,回滚可能会造成一个缺口,但通过获取序列ID并在通常情况下不会回滚的事务中插入它,可以避免这种情况。例如,创建发票,我将插入发票记录,完成所有库存和会计修改,然后请求发票编号,并使用发票ID更新发票记录(发票表具有单独的内部PK)。 –

+0

@ Andres Olarte - 这是一个很好的想法!似乎最简单的实现 – murison

2

你要么锁定表插入,和/或需要有重试代码。没有其他选择可用。如果你停下来想想可以发生的事情:

  1. 并行处理回滚
  2. 锁定超时

,你就会明白为什么。

17

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

前段时间我写了一篇文章。它针对甲骨文,但其实是关于无间隙数字的基本原则,我认为这同样适用于此。

好吧,它又发生了。有人拥有asked如何实现生成无间隙数字序列的要求,并且一群可以说话的人可以说(这里我稍微解释一下)(这里稍微解释一下),这会损害系统性能,这很少是有效的要求,谁写的要求是白痴等等等等等等。

正如我在线程中指出的那样,有时候真正的法律要求是生成无间隙数字序列。英国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调用中插入行并自动提交插入?

Original article

+0

以及我不知道这是如何帮助我在哪里我postgresql,我看到数据在哪里开始交易。 – GorillaApe

+0

Oralce和PostgreSQL具有相同的读取一致性模型,因此锁定访问数字生成器的原理相同。 –

+0

好,那么我需要什么类型的锁?而这怎么可能在序列化隔离级别上工作? – GorillaApe