2

証明書番号を保持するテーブルがあります。各証明書は、一連の文字とシリアル番号で構成されています。証明書は一意である必要があり、ギャップは許されません。つまり、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 ()

残念ながら、これはスレッドセーフではありません。このスクリプトを 2 つのシェル (A と B など) で 2 回開始すると、次のようになります。

  • シェル A でスクリプトを開始すると、 のトランザクション内に保持されinput ()ます。
  • シェル B でスクリプトを開始すると、 のトランザクション内に保持されinput ()ます。
  • シェル A で Enter キーを押すと、トランザクションがコミットされます。
  • この正確な瞬間に、シェル B が (expected) をスローしpostgresql.exceptions.UniqueError: duplicate key value violates unique constraint "Certificate_series_serial_key"ます。

特定の一連の文字に基づいて、スレッドセーフな方法で次の証明書をこのテーブルに挿入するにはどうすればよいですか?

4

2 に答える 2

2

ギャップのないシーケンスを作成しようとしています。そこにはたくさんの情報があります。これが私が少し前にこのトピックについて書いた答えであり、別の答えがここにあります。

証明書テーブルについては正しい考えがありますが、証明書ではなく、証明書シリーズ番号のテーブルであることを明確にする名前を検討します。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通常の操作を行うのと同じですが、より便利で簡単です。UPDATERETURNING

まだ存在していない場合、これによって新しいシリーズが作成されないことに気付くでしょう。正直なところ、あなたの場合の最善のアプローチは、スペースが非常に限られているため、使用する可能性のあるすべてのシリーズを事前に割り当てることです。より複雑なケースでは、アップサートロジックを使用できますが、スムーズに機能させるのはかなり難しく、トランザクションの再試行に備える必要があります。http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/を参照してください

トランザクションが1つのトランザクションで複数のシリーズを使用する場合、シリーズIDのアルファベット順など、常に同じ順序でシリーズからシリアルを取得するように細心の注意を払わない限り、互いにデッドロックする可能性があることに注意してください。

于 2012-12-18T23:19:49.577 に答える
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
于 2012-12-18T16:22:53.737 に答える