sql >> Databáze >  >> RDS >> PostgreSQL

Vypočítat další primární klíč – konkrétního formátu

Vypadá to jako varianta problému sekvence bez mezer; také vidět zde.

Sekvence bez mezer mají vážné problémy s výkonem a souběžností.

Velmi dobře přemýšlejte o tom, co se stane, když dojde k více vložkám najednou. Musíte být připraveni opakovat neúspěšné vložení nebo LOCK TABLE myTable IN EXCLUSIVE MODE před INSERT takže pouze jeden INSERT může být současně v letu.

Použijte sekvenční tabulku se zamykáním řádků

Co bych v této situaci udělal:

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;
$$;

pak získat ID:

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

Tento přístup znamená, že pouze jedna transakce může vždy vkládat řádek s jakýmkoli daným párem (úroveň, režim) najednou, ale myslím, že je to bez závodů.

Pozor na zablokování

Stále existuje problém, kdy se dvě souběžné transakce mohou zablokovat, pokud se pokusí vložit řádky v jiném pořadí. Neexistuje žádná jednoduchá oprava; Musíte si buď objednat vložky tak, abyste vždy vložili nízkou úroveň a režim před vysokou, provést jednu vložku na transakci, nebo žít se zablokováním a zkusit to znovu. Osobně bych udělal to druhé.

Příklad problému se dvěma relacemi psql. Nastavení je:

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

poté ve dvou relacích:

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));

Všimnete si, že druhá vložka v relaci 2 zamrzne, aniž by se vrátila, protože čeká na zámek držený relací 1. Když se relace 1 pokusí získat zámek držený relací 2 ve své druhé vložce, bude také pověsit. Nelze dosáhnout žádného pokroku, takže po sekundě nebo dvou PostgreSQL detekuje uváznutí a přeruší jednu z transakcí, což umožní druhé pokračovat:

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

Váš kód musí být buď připraven, aby to zvládl, a opakujte celou transakci , nebo se musí vyhnout uváznutí pomocí transakcí s jedním vložením nebo pečlivým řazením.

Automatické vytváření neexistujících párů (úroveň, kód)

BTW, pokud chcete kombinace (úroveň, kód), které ještě neexistují v sequence_numbers tabulka, která má být vytvořena při prvním použití, je překvapivě komplikované najít správnou, protože jde o variantu problému upsert. Osobně bych upravil get_next_seqno vypadat takto:

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;

$$;

Tento kód může selhat, takže musíte být vždy připraveni na opakování transakce. Jak vysvětluje tento depeszův článek, jsou možné robustnější přístupy, ale obvykle to nestojí za to. Jak je napsáno výše, pokud se dvě transakce současně pokusí přidat stejný nový pár (úroveň, kód), jedna selže s:

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


  1. Rozšíření použití DBCC CLONEDATABASE

  2. Matematické funkce serveru SQL (úplný seznam)

  3. Co je nového v MariaDB Cluster 10.4

  4. Instalace Neo4j