我有一个保存证书编号的表。每个证书由一系列字母和序列号组成。证书必须是唯一的且不允许有间隙,即如果 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)上启动此脚本两次,则会发生以下情况:
- 我在 shell A 上启动脚本,它保存在
input ()
处的事务中。 - 我在 shell B 上启动脚本,它保存在
input ()
处的事务中。 - 我在 shell A 上按 Enter 键,事务就会提交。
- 此时,shell B 抛出一个(预期的)
postgresql.exceptions.UniqueError:重复的键值违反了唯一约束“Certificate_series_serial_key”
。
如何根据给定的系列字母以线程安全的方式将下一个证书插入到该表中?
最佳答案
您正在尝试创建一个无缝序列。那里有很多关于它的信息。 Here's an answer I wrote on the topic a while ago 和另一个 here 。
您对证书表的想法是正确的,尽管我会考虑使用一个名称来更清楚地表明它是证书序列号表,而不是证书表。例如certificate_series_number
。
要可靠地生成新 ID,您需要在事务中执行以下操作:
curs.execute("UPDATE certificate SET serial = serial + 1 WHERE series = %s RETURNING serial")
newserial = curs.fetchone()[0]
UPDATE
将锁定该series
的行,这会导致对该series
的其他更新阻塞,直到此事务提交或回滚。它不会干扰影响其他系列
的并发事务。
这基本上相当于执行SELECT ... FOR UPDATE
,然后执行常规UPDATE
,而不需要RETURNING
,只是更方便,而且更简单。
您会注意到,如果新系列尚不存在,则这不会创建新系列。老实说,对于您的情况,最好的方法是预先分配您可能使用的所有系列,因为空间非常有限。在更复杂的情况下,您可以使用 upsert 逻辑,但要使其顺利工作相当困难,并且您必须为事务重试做好准备。请参阅 http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/
请注意,如果事务在单个事务中使用多个系列,则它们可能会相互死锁,除非它们非常小心地始终以相同的顺序从系列中获取序列,例如按系列 ID 的字母顺序排列。
关于sql - 使用 postgres 创建连续的证书编号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13936347/