Sequenties hebben hiaten om gelijktijdige invoegingen mogelijk te maken. Pogingen om hiaten te vermijden of verwijderde ID's opnieuw te gebruiken, veroorzaakt vreselijke prestatieproblemen. Zie de PostgreSQL-wiki FAQ.
PostgreSQL SEQUENCE
s worden gebruikt om ID's toe te wijzen. Deze worden alleen maar groter en ze zijn vrijgesteld van de gebruikelijke regels voor het terugdraaien van transacties, zodat meerdere transacties tegelijkertijd nieuwe ID's kunnen pakken. Dit betekent dat als een transactie terugdraait, die ID's worden "weggegooid"; er wordt geen lijst met "gratis" ID's bijgehouden, alleen de huidige ID-teller. Sequenties worden meestal ook verhoogd als de database onrein wordt afgesloten.
Synthetische sleutels (ID's) zijn zinloos hoe dan ook. Hun volgorde is niet significant, hun enige eigenschap van betekenis is uniciteit. U kunt niet zinvol meten hoe 'ver uit elkaar' twee ID's zijn, en u kunt ook niet zinvol zeggen of de ene groter of kleiner is dan de andere. Het enige wat je kunt doen is "gelijk" of "niet gelijk" zeggen. Al het andere is onveilig. Je moet je niet druk maken om hiaten.
Als je een reeks zonder onderbrekingen nodig hebt die verwijderde ID's opnieuw gebruikt, kun je er een hebben, je moet er alleen een enorme hoeveelheid prestaties voor opgeven - in het bijzonder kun je geen gelijktijdigheid hebben op INSERT
s helemaal niet, omdat u de tafel moet scannen op de laagste gratis ID, en de tafel moet vergrendelen voor schrijven, zodat geen enkele andere transactie dezelfde ID kan claimen. Probeer te zoeken naar "postgresql gapless sequence".
De eenvoudigste benadering is om een tellertabel te gebruiken en een functie die de volgende ID krijgt. Hier is een algemene versie die een tellertabel gebruikt om opeenvolgende, gapless ID's te genereren; het hergebruikt echter geen ID's.
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';
Gebruik:
INSERT INTO dummy(id, blah)
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Merk op dat wanneer een open transactie een ID heeft verkregen, alle andere transacties die proberen om get_next_id
te bellen wordt geblokkeerd totdat de eerste transactie wordt vastgelegd of teruggedraaid. Dit is onvermijdelijk en voor ID's zonder tussenruimte en is inherent aan het ontwerp.
Als u meerdere tellers voor verschillende doeleinden in een tabel wilt opslaan, voegt u gewoon een parameter toe aan de bovenstaande functie, voegt u een kolom toe aan de tellertabel en voegt u een WHERE
toe clausule aan de UPDATE
die overeenkomt met de parameter met de toegevoegde kolom. Op die manier kunt u meerdere onafhankelijk vergrendelde tellerrijen hebben. Doe niet voeg gewoon extra kolommen toe voor nieuwe tellers.
Deze functie hergebruikt geen verwijderde ID's, het voorkomt alleen dat er gaten ontstaan.
Om ID's opnieuw te gebruiken, raad ik aan ... ID's niet opnieuw te gebruiken.
Als het echt moet, kun je dit doen door een ON INSERT OR UPDATE OR DELETE
toe te voegen. trigger op de tabel van interesse die verwijderde ID's toevoegt aan een vrije lijst bijzettafel, en ze verwijdert uit de vrije lijst wanneer ze INSERT
zijn red. Behandel een UPDATE
als een DELETE
gevolgd door een INSERT
. Pas nu de ID-generatiefunctie hierboven aan zodat deze een SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
doet en indien gevonden, DELETE
is die rij. IF NOT FOUND
krijgt zoals gewoonlijk een nieuwe ID van de generatortabel. Hier is een niet-geteste uitbreiding van de vorige functie om hergebruik te ondersteunen:
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;