在Postgres中手动排序列的正确方法是什么?

时间:2012-04-18 13:55:08

标签: sql django postgresql sequence autofield

我有一个SaaS宠物项目用于开发票。在其中,我希望我的客户每个都以1001号票号开头。显然,我不能在Postgres中使用简单的自动字段,只需在该值中添加1000,因为我的所有客户端将共享相同的数据库和相同的{{ 1}} table ..我尝试使用整数列类型并查询(伪SQL)tickets以获取最新数字,然后使用该数字SELECT LATEST number FROM tickets WHERE client_id = [current client ID]获取下一个{{1} }。问题在于,通过并发,两张票很容易以这种方式以相同的数字结尾。我需要能够在Django中或使用原始SQL(使用Bash或其他任何类型的东西)执行此操作的数字。

我不是在寻找一种方法来强迫我的例子发挥作用。我只是在寻找解决我需要为每个客户单独增加票号的问题。

2 个答案:

答案 0 :(得分:5)

我不认为这个问题有“便宜”的解决方案。在多用户环境中唯一安全(但不一定快)的解决方案是为每个客户提供一个“计数器”表,其中包含一行。

在插入新票证之前,每个交易都必须首先锁定客户的条目,如下所示:

UPDATE cust_numbers
  SET current_number = current_number + 1
WHERE cust_id = 42
RETURNING current_number;

这将一步完成三件事

  1. 增加该客户的当前“连续”号码
  2. 锁定该行,以便执行相同操作的其他事务必须等待锁定
  3. 返回该列的新值。
  4. 使用该新号码,您现在可以插入新票证。如果事务已提交,它还将释放cust_numbers表上的锁,因此可以继续“等待数字”的其他事务。

    您可以将这两个步骤(更新...返回和插入)包装到单个存储函数中,以便将此背后的逻辑集中在一起。您的应用程序只会在不知道票号如何生成的情况下调用select insert_ticket(...)

    您可能还希望在customer表上创建一个触发器,以便在创建新客户时自动将行插入cust_numbers表。

    这样做的缺点是您可以有效地序列化为同一客户插入新票证的交易。根据系统中插入的大小,这可能会成为性能问题。

    修改
    这样做的另一个缺点是,您不是强制以可能导致问题的方式插入票证,例如,一位新的开发人员忘了这件事。

答案 1 :(得分:2)

您可以为每个客户创建序列,然后将列的值设置为nextval('name_of_the_sequence')。这实际上是serial的工作原理;您的情况唯一的区别是您不使用列的默认值并且具有多个序列。

在插入新行时创建那些选择正确序列的序列可以通过PL / Pgsql过程很好地完成。