I have a table that holds certificate numbers. Each certificate consists of a series letter and a serial number. Certificates must be unique and no gaps are allowed, i.e. if A-1234 exists, also A-1233 must exist. Each series has its own serials, i.e. A-1234 and B-1234 can happily coexist.
The table holding the information is this:
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
);
Now the problem is how to create the next serial number, given a specific series. Postgres sequences doesn't seem to be the way to go, as these would create gaps if an insert fails for whatever reason or if a transaction, which required a new value from a sequence, is rolled back.
My idea was the following:
#! /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 ()
Unfortunately this is not thread safe. If I start this script twice on two shells (say A and B) the following happens:
- I start the script on shell A, it holds inside the transaction at
input ()
. - I start the script on shell B, it holds inside the transaction at
input ()
. - I press enter on shell A and the transaction commits.
- At this precise moment shell B throws an (expected)
postgresql.exceptions.UniqueError: duplicate key value violates unique constraint "Certificate_series_serial_key"
.
How can I insert the next certificate into this table, based on a given series letter, in a thread safe manner?