sql >> Database teknologi >  >> RDS >> PostgreSQL

Beregn næste primærnøgle - af specifikt format

Dette ligner en variant af det gapless-sekvensproblem; også set her.

Gapless-sekvenser har alvorlige præstations- og samtidighedsproblemer.

Tænk meget godt over, hvad der vil ske, når der sker flere indsættelser på én gang. Du skal være forberedt på at prøve mislykkede indsættelser igen, eller LOCK TABLE myTable IN EXCLUSIVE MODE før INSERT så kun én INSERT kan være i flyvning ad gangen.

Brug en sekvenstabel med rækkelåsning

Hvad jeg ville gøre i denne situation er:

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

derefter for at få et ID:

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

Denne tilgang betyder, at kun én transaktion nogensinde kan indsætte en række med et givet (niveau, tilstand) par ad gangen, men jeg tror, ​​det er race-frit.

Pas på dødvande

Der er stadig et problem, hvor to samtidige transaktioner kan gå i stå, hvis de forsøger at indsætte rækker i en anden rækkefølge. Der er ingen nem løsning på dette; du skal enten bestille dine skær, så du altid indsætter lavt niveau og tilstand før høj, laver et indstik pr. transaktion, eller lever med deadlocks og prøver igen. Personligt ville jeg gøre det sidste.

Eksempel på problemet, med to psql-sessioner. Opsætningen er:

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

derefter i to sessioner:

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

Du vil bemærke, at den anden indsats i session 2 vil hænge uden at vende tilbage, fordi den venter på en lås holdt af session 1. Når session 1 fortsætter med at forsøge at få en lås holdt af session 2 i dens anden indsats, vil den også hænge. Der kan ikke gøres fremskridt, så efter et sekund eller to vil PostgreSQL registrere dødvandet og afbryde en af ​​transaktionerne, så den anden kan fortsætte:

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

Din kode skal enten være forberedt til at håndtere dette og prøve hele transaktionen igen , eller det skal undgå dødvandet ved at bruge en enkelt-indsæt transaktioner eller omhyggelig bestilling.

Automatisk oprettelse af ikke-eksisterende (niveau, kode) par

BTW, hvis du ønsker (niveau, kode) kombinationer, der ikke allerede findes i sequence_numbers tabel, der skal oprettes ved første brug, det er overraskende kompliceret at få rigtigt, da det er en variant af upsert-problemet. Jeg ville personligt ændre get_next_seqno at se sådan ud:

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;

$$;

Denne kode kan mislykkes, så du skal altid være forberedt på at prøve transaktioner igen. Som den depesz-artikel forklarer, er mere robuste tilgange mulige, men normalt ikke det værd. Som skrevet ovenfor, hvis to transaktioner samtidig forsøger at tilføje det samme nye (niveau, kode) par, vil den ene mislykkes med:

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. Sådan løses ORA-02014:kan ikke vælge TIL OPDATERING fra visning med DISTINCT, GROUP BY

  2. Prag PostgreSQL Meetup

  3. Migrering af MySQL til PostgreSQL på AWS RDS, del 1

  4. Lås Oracle-databasen, før du kører Slet/indlæs data-scripts