2

ID のリストを保持するテーブルと、IDName などの他のさまざまな列があります。

テーブルの主キーは ID そのものですが、auto_increment ではありません。したがって、次の主キーを生成/計算できるようにしたいのですが、ひねりがあります。

主キーは特定の形式にする必要があります。つまり、8 桁の ID は次の 3 つの部分で構成されます
<the level><a code><a sequence #><2><777><0123> = 27770123

そのため、テーブルの新しい ID を作成するときに、特定のレベルとコードの次のシーケンス番号が必要です。たとえば、上記の例に従って、コード 777 のレベル 2 の次のシーケンス番号を知りたい場合、結果は ID 27770124 (0124 がシーケンスの次) になります。

どんな助けでも大歓迎です。

4

3 に答える 3

6

これは、ギャップレス シーケンス問題の変形のように見えます。ここにも見られます。

ギャップレス シーケンスには、重大なパフォーマンスと同時実行の問題があります。

一度に複数の挿入が発生した場合に何が起こるかをよく考えてください。失敗した挿入を再試行する準備をするか、一度に1 つしか実行できないようLOCK TABLE myTable IN EXCLUSIVE MODEにする必要があります。INSERTINSERT

行ロックのあるシーケンス テーブルを使用する

この状況で私がすることは次のとおりです。

CREATE TABLE sequence_numbers(
    level integer,
    code integer,
    next_value integer DEFAULT 0 NOT NULL,
    PRIMARY KEY (level,code),
    CONSTRAINT level_must_be_one_digit CHECK (level BETWEEN 0 AND 9),
    CONSTRAINT code_must_be_three_digits CHECK (code BETWEEN 0 AND 999),
    CONSTRAINT value_must_be_four_digits CHECK (next_value BETWEEN 0 AND 9999)
);

INSERT INTO sequence_numbers(level,code) VALUES (2,777);

CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$
    UPDATE sequence_numbers 
    SET next_value = next_value + 1
    WHERE level = $1 AND code = $2
    RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;
$$;

次に ID を取得します。

INSERT INTO myTable (sequence_number, blah)
VALUES (get_next_seqno(2,777), blah);

このアプローチは、一度に 1 つのトランザクションのみが特定の (レベル、モード) ペアで行を挿入できることを意味しますが、競合はないと思います。

デッドロックに注意

2 つの同時トランザクションが別の順序で行を挿入しようとすると、デッドロックする可能性があるという問題がまだ残っています。これを簡単に修正することはできません。常にローレベルとモードをハイの前に挿入するように挿入を注文するか、トランザクションごとに 1 つの挿入を行うか、デッドロックを抱えて再試行する必要があります。個人的には後者にします。

2 つの psql セッションでの問題の例。セットアップは次のとおりです。

CREATE TABLE myTable(seq_no integer primary key);
INSERT INTO sequence_numbers VALUES (1,666)

次に、2 つのセッションで:

SESSION 1                       SESSION 2

BEGIN;
                                BEGIN;

INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(2,777));
                                INSERT INTO myTable(seq_no)
                                VALUES(get_next_seqno(1,666));

                                INSERT INTO myTable(seq_no)
                                VALUES(get_next_seqno(2,777));

INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(1,666));

セッション 2 の 2 番目の挿入は、セッション 1 が保持するロックを待機しているため、戻ることなくハングすることに気付くでしょう。セッション 1 が 2 番目の挿入でセッション 2 が保持するロックを取得しようとすると、それも下がる。処理を進めることができないため、1 ~ 2 秒後に PostgreSQL がデッドロックを検出し、トランザクションの 1 つを中止して、もう 1 つのトランザクションを続行できるようにします。

ERROR:  deadlock detected
DETAIL:  Process 16723 waits for ShareLock on transaction 40450; blocked by process 18632.
Process 18632 waits for ShareLock on transaction 40449; blocked by process 16723.
HINT:  See server log for query details.
CONTEXT:  SQL function "get_next_seqno" statement 1

これを処理してトランザクション全体を再試行できるようにコードを準備するか、単一挿入トランザクションまたは慎重な順序付けを使用してデッドロックを回避する必要があります。

存在しない (レベル、コード) ペアの自動作成

ところで、テーブルにまだ存在しない (レベル、コード) の組み合わせを最初の使用時に作成したい場合、これはアップサートの問題sequence_numbersの変形であるため、驚くほど複雑です。私は個人的に次のように変更します。get_next_seqno

CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$

    -- add a (level,code) pair if it isn't present.
    -- Racey, can fail, so you have to be prepared to retry
    INSERT INTO sequence_numbers (level,code)
    SELECT $1, $2
    WHERE NOT EXISTS (SELECT 1 FROM sequence_numbers WHERE level = $1 AND code = $2);

    UPDATE sequence_numbers 
    SET next_value = next_value + 1
    WHERE level = $1 AND code = $2
    RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;

$$;

このコードは失敗する可能性があるため、常にトランザクションを再試行できるようにしておく必要があります。その depesz の記事が説明しているように、より堅牢なアプローチが可能ですが、通常は価値がありません。上記のように、2 つのトランザクションが同じ新しい (レベル、コード) ペアを同時に追加しようとすると、1 つが次のエラーで失敗します。

ERROR:  duplicate key value violates unique constraint "sequence_numbers_pkey"
DETAIL:  Key (level, code)=(0, 555) already exists.
CONTEXT:  SQL function "get_next_seqno" statement 1
于 2012-09-17T09:39:17.277 に答える
0

わかりました、これまでのところ、これは私の要件を満たすと思います (より最適な方法について提案がない限り?)

SELECT ID 
FROM myTable 
WHERE ID > 27770000 
  AND ID < 27780000 
ORDER BY ID DESC 
LIMIT 1
于 2012-09-17T09:20:45.010 に答える
0

アプリケーションの需要が非常に高い場合を除き、衝突の数は非常に少なくなります。そのため、キー エラーが発生した場合は単純に再試行します。

select coalesce(max(id), 27770000) + 1
from myTable
where id / 10000 = 2777

レベル/コードがまだ存在しない場合に備えて、合体があります。

于 2012-09-17T11:13:46.930 に答える