我有一张包含证书编号的表格。每个证书都包含一个系列字母和一个序列号。证书必须是唯一的,不允许有间隙,即如果存在A-1234,也必须存在A-1233 。每个系列都有自己的系列,即A-1234和B-1234可以很好地共存。
包含信息的表是:
CREATE TABLE "Certificate"
(
id serial NOT NULL,
series character(1) NOT NULL,
serial integer NOT NULL,
CONSTRAINT "Certificate_pkey" PRIMARY KEY (id ),
CONSTRAINT "Certificate_series_serial_key" UNIQUE (series , serial )
)
WITH (
OIDS=FALSE
);
现在问题是如何在给定特定系列的情况下创建下一个序列号。 Postgres序列似乎不是可行的方法,因为如果插入因任何原因而失败,或者如果需要从序列中获取新值的事务被回滚,这些将会产生间隙。
我的想法如下:
#! /usr/bin/env python3.2
import postgresql.driver.dbapi20 as pgdb
credentials = #my db credentials
SERIES = 'B'
dbcon = pgdb.connect (**credentials)
cursor = dbcon.cursor ()
cursor.execute ('''insert into "Certificate" ("series", "serial")
(select %s, coalesce (max ("serial"), 0) + 1
from "Certificate"
where "series" = %s) ''', [SERIES] * 2)
input () #just to keep the transaction open some time
cursor.close ()
dbcon.commit ()
dbcon.close ()
不幸的是,这不是线程安全的。如果我在两个shell(比如A和B)上两次启动此脚本,则会发生以下情况:
input ()
的交易中。input ()
的交易中。postgresql.exceptions.UniqueError: duplicate key value violates unique constraint "Certificate_series_serial_key"
。如何根据给定的系列字母以线程安全的方式将下一个证书插入此表?
答案 0 :(得分:2)
抓住异常并再试一次
while True:
try:
cursor.execute ('''
insert into "Certificate" ("series", "serial")
select %s, coalesce (max ("serial"), 0) + 1
from "Certificate"
where "series" = %s
''', [SERIES] * 2)
dbcon.commit ()
break
except postgresql.exceptions.UniqueError:
continue
答案 1 :(得分:2)
您正在尝试创建无间隙序列。那里有很多关于它的信息。 Here's an answer I wrote on the topic a while ago和另一个here。
你对Certificate表有正确的想法,虽然我会考虑一个名称,它更清楚它是一个证书系列号码表,而不是证书。例如certificate_series_number
。
在交易中,您要做的是如何可靠地生成新ID:
curs.execute("UPDATE certificate SET serial = serial + 1 WHERE series = %s RETURNING serial")
newserial = curs.fetchone()[0]
UPDATE
会锁定series
的行,导致该series
的其他更新被阻止,直到此事务提交或回滚为止。它不会干扰影响其他 series
的并发事务。
这基本上相当于在没有SELECT ... FOR UPDATE
的情况下执行UPDATE
后跟常规RETURNING
,这样更方便,更简单。
您会注意到,如果新系列尚不存在,则不会创建新系列。老实说,在你的情况下,最好的方法是预先分配你可能使用的所有系列,因为空间是如此受限制。在更复杂的情况下,您可以使用upsert逻辑,但要使其顺利运行相当困难,并且您必须为事务重试做好准备。见http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/
请注意,如果事务在单个事务中使用多个系列,则它们可能会相互死锁,除非他们非常小心地始终以相同的顺序从序列中获取序列号,例如按字母顺序按序列ID获取。