Sekvence mají mezery, které umožňují souběžné vkládání. Pokus vyhnout se mezerám nebo znovu použít smazaná ID vytváří hrozné problémy s výkonem. Podívejte se na PostgreSQL wiki FAQ.
PostgreSQL SEQUENCE
s se používají k přidělování ID. Ty se neustále zvyšují a jsou vyňaty z obvyklých pravidel pro vrácení transakcí, aby bylo umožněno více transakcím získat nová ID současně. To znamená, že pokud se transakce vrátí zpět, tato ID jsou „vyhozena“; není uchováván žádný seznam „bezplatných“ ID, pouze aktuální počítadlo ID. Sekvence se také obvykle zvýší, pokud se databáze vypne nečistě.
Syntetické klíče (ID) jsou bezvýznamné tak jako tak. Jejich pořadí není významné, jejich jedinou významnou vlastností je jedinečnost. Nemůžete smysluplně změřit, jak „daleko od sebe“ jsou dvě ID, ani nemůžete smysluplně říci, zda je jedno větší nebo menší než druhé. Jediné, co můžete udělat, je říci „rovný“ nebo „nejen“. Cokoli jiného je nebezpečné. Mezery by vás neměly zajímat.
Pokud potřebujete sekvenci bez mezer, která znovu používá smazaná ID, můžete ji mít, jen se kvůli ní musíte vzdát obrovského množství výkonu – konkrétně nemůžete mít žádnou souběžnost na INSERT
s vůbec, protože musíte naskenovat tabulku pro nejnižší volné ID a uzamknout tabulku pro zápis, takže žádná jiná transakce nemůže požadovat stejné ID. Zkuste vyhledat „postgresql gapless sequence“.
Nejjednodušší přístup je použít tabulku čítačů a funkci, která získá další ID. Zde je zobecněná verze, která používá tabulku čítačů ke generování po sobě jdoucích ID bez mezer; nepoužívá však znovu ID.
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';
Použití:
INSERT INTO dummy(id, blah)
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Pamatujte, že když jedna otevřená transakce získá ID, všechny ostatní transakce, které se pokoušejí volat get_next_id
se zablokuje, dokud nebude 1. transakce potvrzena nebo vrácena zpět. To je u ID bez mezer nevyhnutelné a je to záměrné.
Pokud chcete do tabulky uložit více čítačů pro různé účely, stačí přidat parametr do výše uvedené funkce, přidat sloupec do tabulky čítačů a přidat WHERE
klauzule do UPDATE
který odpovídá parametru přidanému sloupci. Tímto způsobem můžete mít více nezávisle uzamčených protiřádků. Ne stačí přidat další sloupce pro nové čítače.
Tato funkce znovu nepoužívá smazaná ID, pouze zabraňuje vkládání mezer.
Chcete-li znovu použít ID, doporučuji ... nepoužívejte znovu ID.
Pokud opravdu musíte, můžete tak učinit přidáním ON INSERT OR UPDATE OR DELETE
spouštěč v tabulce zájmu, který přidá smazaná ID do postranní tabulky volného seznamu a odebere je z tabulky volného seznamu, když jsou INSERT
vyd. Ošetřit UPDATE
jako DELETE
následuje INSERT
. Nyní upravte výše uvedenou funkci generování ID tak, aby provedla SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
a pokud je nalezen, DELETE
je ten řádek. IF NOT FOUND
získá nové ID z tabulky generátoru jako obvykle. Zde je netestované rozšíření předchozí funkce na podporu opětovného použití:
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;