PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Oblicz następny klucz podstawowy - o określonym formacie

Wygląda to na wariant problemu sekwencji bez przerw; również tutaj.

Sekwencje bez przerw mają poważne problemy z wydajnością i współbieżnością.

Zastanów się, co się stanie, gdy wiele wstawek zostanie wykonanych jednocześnie. Musisz być przygotowany na ponowną próbę wstawiania nieudanych prób lub LOCK TABLE myTable IN EXCLUSIVE MODE przed INSERT więc tylko jeden INSERT może być w locie na raz.

Użyj tabeli sekwencji z blokowaniem wierszy

Co bym zrobił w tej sytuacji to:

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

następnie, aby uzyskać identyfikator:

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

Takie podejście oznacza, że ​​tylko jedna transakcja może wstawić wiersz z daną parą (poziom, tryb) na raz, ale myślę, że jest to wolne od wyścigu.

Uważaj na impas

Nadal istnieje problem polegający na tym, że dwie współbieżne transakcje mogą się zakleszczyć, jeśli spróbują wstawić wiersze w innej kolejności. Nie ma na to łatwego rozwiązania; musisz albo zamówić wkładki, aby zawsze wkładać niski poziom i tryb przed wysokim, wykonać jedną wkładkę na transakcję lub żyć z zakleszczeniami i ponowić próbę. Osobiście zrobiłbym to drugie.

Przykład problemu z dwiema sesjami psql. Konfiguracja to:

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

następnie w dwóch sesjach:

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

Zauważysz, że druga wkładka w sesji 2 zawiesi się bez powrotu, ponieważ czeka na blokadę utrzymywaną przez sesję 1. Kiedy sesja 1 próbuje uzyskać blokadę utrzymywaną przez sesję 2 w drugiej wkładce, również to zrobi powiesić. Nie można poczynić postępu, więc po sekundzie lub dwóch PostgreSQL wykryje zakleszczenie i przerwie jedną z transakcji, umożliwiając kontynuację drugiej:

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

Twój kod musi być przygotowany do obsługi tego i ponowić całą transakcję , lub musi uniknąć impasu za pomocą transakcji jednorazowych lub starannego zamawiania.

Automatyczne tworzenie nieistniejących par (poziom, kod)

BTW, jeśli chcesz (poziom, kod) kombinacje, które jeszcze nie istnieją w sequence_numbers stół, który ma zostać utworzony przy pierwszym użyciu, jest zaskakująco skomplikowany, ponieważ jest to wariant problemu upsert. Osobiście zmodyfikowałbym get_next_seqno wyglądać tak:

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;

$$;

Ten kod może się nie powieść, więc zawsze musisz być przygotowany na ponowną próbę transakcji. Jak wyjaśnia ten artykuł w Depesz, bardziej solidne podejścia są możliwe, ale zwykle nie są tego warte. Jak napisano powyżej, jeśli dwie transakcje jednocześnie próbują dodać tę samą nową parę (poziom, kod), jedna zakończy się niepowodzeniem z:

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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak wyłączyć integralność referencyjną w Postgres 8.2?

  2. Dostrajanie operacji wejścia/wyjścia (I/O) dla PostgreSQL

  3. Wyświetlaj wybrane wyniki pionowo w psql, tak jak robi to MySQL \G

  4. Jak wybrać id z maksymalną grupą dat według kategorii w PostgreSQL?

  5. Hibernacja użycia sekwencji PostgreSQL nie wpływa na tabelę sekwencji