sql - postgresql 生成没有间隙的序列

标签 sql postgresql

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

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

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

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

最佳答案

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

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

Well, it’s happened again. Someone has asked how to implement a requirement to generate a gap-free series of numbers and a swarm of nay-sayers have descended on them to say (and here I paraphrase slightly) that this will kill system performance, that’s it’s rarely a valid requirement, that whoever wrote the requirement is an idiot blah blah blah.

As I point out on the thread, it is sometimes a genuine legal requirement to generate gap-free series of numbers. Invoice numbers for the 2,000,000+ organisations in the UK that are VAT (sales tax) registered have such a requirement, and the reason for this is rather obvious: that it makes it more difficult to hide the generation of revenue from tax authorities. I’ve seen comments that it is a requirement in Spain and Portugal, and I’d not be surprised if it was not a requirement in many other countries.

So, if we accept that it is a valid requirement, under what circumstances are gap-free series* of numbers a problem? Group-think would often have you believe that it always is, but in fact it is only a potential problem under very particular circumstances.

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

If all of these requirements must be met then you have a point of serialisation in your application, and we’ll discuss that in a moment.

First let’s talk about methods of implementing a series-of-numbers requirement if you can drop any one of those requirements.

If your series of numbers can have gaps (and you have multiple processes requiring instant generation of the number) then use an Oracle Sequence object. They are very high performance and the situations in which gaps can be expected have been very well discussed. It is not too challenging to minimise the amount of numbers skipped by making design efforts to minimise the chance of a process failure between generation of the number and commiting the transaction, if that is important.

If you do not have multiple processes creating the entities (and you need a gap-free series of numbers that must be instantly generated), as might be the case with the batch generation of invoices, then you already have a point of serialisation. That in itself may not be a problem, and may be an efficient way of performing the required operation. Generating the gap-free numbers is rather trivial in this case. You can read the current maximum value and apply an incrementing value to every entity with a number of techniques. For example if you are inserting a new batch of invoices into your invoice table from a temporary working table you might:

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

Of course you would protect your process so that only one instance can run at a time (probably with DBMS_Lock if you're using Oracle), and protect the invoice# with a unique key contrainst, and probably check for missing values with separate code if you really, really care.

If you do not need instant generation of the numbers (but you need them gap-free and multiple processes generate the entities) then you can allow the entities to be generated and the transaction commited, and then leave generation of the number to a single batch job. An update on the entity table, or an insert into a separate table.

So if we need the trifecta of instant generation of a gap-free series of numbers by multiple processes? All we can do is to try to minimise the period of serialisation in the process, and I offer the following advice, and welcome any additional advice (or counter-advice of course).

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

Original article

关于sql - postgresql 生成没有间隙的序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19004453/

相关文章:

c# - 如何向/从 SQL Server 存储过程发送和接收参数

MySQL : sum of every day

python - 执行函数后删除临时表

postgresql - 存储一组 postgres 枚举值以供重用的正确抽象是什么?

postgresql - PlayFramework 2 + Ebean - 原始 Sql 更新查询 - 对数据库没有影响

postgresql - 在 rust-postgres 中使用固定精度数字的正确方法是什么?

sql - 将连接与 ORDER BY 相结合

php - INNODB 引擎中的 MySql AUTO_INCRMENT 作为第二列

mysql - DEFAULT 在 SET 数据中不起作用

sql - DB2日期格式