Sekvenser har mellemrum for at tillade samtidige inserts. Forsøg på at undgå huller eller genbruge slettede ID'er skaber forfærdelige ydeevneproblemer. Se PostgreSQL wiki Ofte stillede spørgsmål.
PostgreSQL SEQUENCE
s bruges til at tildele ID'er. Disse stiger kun nogensinde, og de er undtaget fra de sædvanlige regler for tilbagerulning af transaktioner for at tillade flere transaktioner at få fat i nye id'er på samme tid. Dette betyder, at hvis en transaktion ruller tilbage, bliver disse ID'er "smidt væk"; der er ingen liste over "gratis" ID'er, kun den aktuelle ID-tæller. Sekvenser øges normalt også, hvis databasen lukkes urent.
Syntetiske nøgler (ID'er) er meningsløse alligevel. Deres rækkefølge er ikke signifikant, deres eneste egenskab af betydning er unikhed. Du kan ikke meningsfuldt måle, hvor "langt fra hinanden" to ID'er er, og du kan heller ikke meningsfuldt sige, om det ene er større eller mindre end det andet. Alt du kan gøre er at sige "lige" eller "ikke lige". Alt andet er usikkert. Du skal være ligeglad med huller.
Hvis du har brug for en hulfri sekvens, der genbruger slettede ID'er, kan du have en, du skal bare give afkald på en enorm mængde ydeevne for det - især kan du ikke have nogen samtidighed på INSERT
s overhovedet, fordi du skal scanne bordet for det laveste ledige ID, låse bordet til skrivning, så ingen anden transaktion kan gøre krav på det samme ID. Prøv at søge efter "postgresql gapless sequence".
Den enkleste fremgangsmåde er at bruge en tællertabel og en funktion, der får det næste ID. Her er en generaliseret version, der bruger en tællertabel til at generere på hinanden følgende id'er uden gap; den genbruger dog ikke ID'er.
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';
Brug:
INSERT INTO dummy(id, blah)
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Bemærk, at når en åben transaktion har fået et ID, vil alle andre transaktioner, der forsøger at kalde get_next_id
vil blokere, indtil den 1. transaktion forpligter sig eller ruller tilbage. Dette er uundgåeligt og for id'er uden hul og er designet.
Hvis du ønsker at gemme flere tællere til forskellige formål i en tabel, skal du blot tilføje en parameter til ovenstående funktion, tilføje en kolonne til tællertabellen og tilføje en WHERE
klausul til UPDATE
der matcher parameteren med den tilføjede kolonne. På den måde kan du have flere uafhængigt låste tællerrækker. Gør ikke blot tilføje ekstra kolonner til nye tællere.
Denne funktion genbruger ikke slettede ID'er, den undgår blot at indføre huller.
For at genbruge ID'er anbefaler jeg ... ikke at genbruge ID'er.
Hvis du virkelig skal, kan du gøre det ved at tilføje en ON INSERT OR UPDATE OR DELETE
trigger på interessetabellen, der tilføjer slettede id'er til en frilistesidetabel og fjerner dem fra frilistetabellen, når de er INSERT
udg. Behandl en UPDATE
som en DELETE
efterfulgt af en INSERT
. Rediger nu ID-genereringsfunktionen ovenfor, så den gør en SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
og hvis fundet, DELETE
er den række. IF NOT FOUND
får et nyt ID fra generatortabellen som normalt. Her er en utestet udvidelse af den tidligere funktion for at understøtte genbrug:
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;