Sekwencje mają luki umożliwiające jednoczesne wstawianie. Próba uniknięcia luk lub ponownego wykorzystania usuniętych identyfikatorów powoduje straszne problemy z wydajnością. Zobacz FAQ wiki PostgreSQL.
PostgreSQL SEQUENCE
s służą do przydzielania identyfikatorów. Te tylko stale rosną i są zwolnione ze zwykłych reguł wycofywania transakcji, aby umożliwić wielu transakcjom pobieranie nowych identyfikatorów w tym samym czasie. Oznacza to, że jeśli transakcja zostanie wycofana, te identyfikatory zostaną „wyrzucone”; nie jest przechowywana lista „darmowych” identyfikatorów, tylko bieżący licznik identyfikatorów. Sekwencje są również zwykle zwiększane, jeśli baza danych jest nieczysto zamykana.
Klucze syntetyczne (identyfikatory) są bez znaczenia W każdym razie. Ich kolejność nie jest znacząca, jedyną ich cechą znaczenia jest wyjątkowość. Nie możesz w znaczący sposób zmierzyć, jak „daleko od siebie” znajdują się dwa identyfikatory, ani nie możesz sensownie powiedzieć, czy jeden jest większy czy mniejszy od drugiego. Wszystko, co możesz zrobić, to powiedzieć „równe” lub „nie równe”. Wszystko inne jest niebezpieczne. Nie powinieneś przejmować się lukami.
Jeśli potrzebujesz sekwencji bez przerw, która ponownie wykorzystuje usunięte identyfikatory, możesz je mieć, po prostu musisz zrezygnować z ogromnej wydajności - w szczególności nie możesz mieć żadnej współbieżności na INSERT
s w ogóle, ponieważ musisz przeskanować tabelę w poszukiwaniu najniższego wolnego identyfikatora, blokując tabelę do zapisu, aby żadna inna transakcja nie mogła zażądać tego samego identyfikatora. Spróbuj wyszukać „sekwencję bez przerw postgresql”.
Najprostszym podejściem jest użycie tabeli liczników i funkcji, która pobiera następny identyfikator. Oto uogólniona wersja, która używa tabeli liczników do generowania kolejnych identyfikatorów bez przerw; jednak nie wykorzystuje ponownie identyfikatorów.
CREATE TABLE thetable_id_counter ( last_id integer not null );
INSERT INTO thetable_id_counter VALUES (0);
CREATE OR REPLACE FUNCTION get_next_id(countertable regclass, countercolumn text) RETURNS integer AS $$
DECLARE
next_value integer;
BEGIN
EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
RETURN next_value;
END;
$$ LANGUAGE plpgsql;
COMMENT ON get_next_id(countername regclass) IS 'Increment and return value from integer column $2 in table $1';
Użycie:
INSERT INTO dummy(id, blah)
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Zwróć uwagę, że gdy jedna otwarta transakcja uzyskała identyfikator, wszystkie inne transakcje, które próbują wywołać get_next_id
zablokuje się do momentu zatwierdzenia lub wycofania pierwszej transakcji. Jest to nieuniknione i w przypadku identyfikatorów bez przerw i jest zgodne z projektem.
Jeśli chcesz przechowywać wiele liczników do różnych celów w tabeli, po prostu dodaj parametr do powyższej funkcji, dodaj kolumnę do tabeli liczników i dodaj WHERE
klauzula do UPDATE
który dopasowuje parametr do dodanej kolumny. W ten sposób możesz mieć wiele niezależnie blokowanych rzędów liczników. Nie po prostu dodaj dodatkowe kolumny dla nowych liczników.
Ta funkcja nie wykorzystuje ponownie usuniętych identyfikatorów, po prostu zapobiega wprowadzaniu luk.
Aby ponownie użyć identyfikatorów, radzę... nie używać ponownie identyfikatorów.
Jeśli naprawdę musisz, możesz to zrobić, dodając ON INSERT OR UPDATE OR DELETE
wyzwalacz w tabeli zainteresowania, który dodaje usunięte identyfikatory do tabeli pobocznej free-list i usuwa je z tabeli free-list, gdy są INSERT
wyd. Traktuj UPDATE
jako DELETE
po którym następuje INSERT
. Teraz zmodyfikuj powyższą funkcję generowania identyfikatorów, tak aby wykonywała SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
a jeśli zostanie znaleziony, DELETE
s ten wiersz. IF NOT FOUND
pobiera nowy identyfikator z tabeli generatora w normalny sposób. Oto nieprzetestowane rozszerzenie poprzedniej funkcji do obsługi ponownego użycia:
CREATE OR REPLACE FUNCTION get_next_id_reuse(countertable regclass, countercolumn text, freelisttable regclass, freelistcolumn text) RETURNS integer AS $$
DECLARE
next_value integer;
BEGIN
EXECUTE format('SELECT %I FROM %s FOR UPDATE LIMIT 1', freelistcolumn, freelisttable) INTO next_value;
IF next_value IS NOT NULL THEN
EXECUTE format('DELETE FROM %s WHERE %I = %L', freelisttable, freelistcolumn, next_value);
ELSE
EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
END IF;
RETURN next_value;
END;
$$ LANGUAGE plpgsql;