sql >> Databasteknik >  >> RDS >> PostgreSQL

PostgreSQL sekvenser utan mellanrum

Sekvenser har luckor för att tillåta samtidiga insättningar. Att försöka undvika luckor eller att återanvända borttagna ID skapar fruktansvärda prestandaproblem. Se PostgreSQL wiki FAQ.

PostgreSQL SEQUENCE s används för att tilldela ID. Dessa ökar bara någonsin, och de är undantagna från de vanliga reglerna för återställning av transaktioner för att tillåta flera transaktioner att ta nya ID:n samtidigt. Detta betyder att om en transaktion rullar tillbaka, "kastas dessa ID"; det finns ingen lista över "gratis" ID:n som bevaras, bara den aktuella ID-räknaren. Sekvenser ökas vanligtvis också om databasen stängs av orent.

Syntetiska nycklar (ID) är meningslösa i alla fall. Deras ordning är inte signifikant, deras enda egenskap av betydelse är unikhet. Du kan inte på ett meningsfullt sätt mäta hur "långt ifrån varandra" två ID:n är, och du kan inte heller på ett meningsfullt sätt säga om det ena är större eller mindre än det andra. Allt du kan göra är att säga "lika" eller "inte lika". Allt annat är osäkert. Du bör inte bry dig om luckor.

Om du behöver en sekvens utan luckor som återanvänder borttagna ID:n kan du ha ett, du måste bara ge upp en enorm prestanda för det - i synnerhet kan du inte ha någon samtidighet på INSERT s överhuvudtaget, eftersom du måste skanna tabellen efter det lägsta lediga ID, låsa tabellen för skrivning så att ingen annan transaktion kan göra anspråk på samma ID. Försök att söka efter "postgresql gapless sequence".

Det enklaste sättet är att använda en räknartabell och en funktion som får nästa ID. Här är en generaliserad version som använder en räknartabell för att generera på varandra följande ID:n utan luckor; den återanvänder dock inte ID:n.

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

Användning:

INSERT INTO dummy(id, blah) 
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );

Observera att när en öppen transaktion har erhållit ett ID kommer alla andra transaktioner som försöker anropa get_next_id kommer att blockeras tills den första transaktionen genomförs eller återgår. Detta är oundvikligt och för ID:n utan luckor och är designat.

Om du vill lagra flera räknare för olika ändamål i en tabell, lägg bara till en parameter till funktionen ovan, lägg till en kolumn i räknartabellen och lägg till en WHERE klausul till UPDATE som matchar parametern med den tillagda kolumnen. På så sätt kan du ha flera oberoende låsta räknarrader. Gör inte lägg bara till extra kolumner för nya räknare.

Den här funktionen återanvänder inte borttagna ID:n, den undviker bara att skapa luckor.

För att återanvända ID:n rekommenderar jag ... att inte återanvända ID:n.

Om du verkligen måste kan du göra det genom att lägga till en ON INSERT OR UPDATE OR DELETE utlösare på intressetabellen som lägger till borttagna ID:n till en sidotabell för frilistan och tar bort dem från tabellen för frilistan när de är INSERT ed. Behandla en UPDATE som en DELETE följt av en INSERT . Ändra nu ID-genereringsfunktionen ovan så att den gör ett SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 och om hittas, DELETE är den raden. IF NOT FOUND får ett nytt ID från generatortabellen som vanligt. Här är en oprövad förlängning av den tidigare funktionen för att stödja återanvändning:

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;



  1. Lägger till dict-objekt till postgresql

  2. SQL Server-isoleringsnivåer:A-serien

  3. Jämföra virtuella molnmaskiner med hanterad molndatabas

  4. En lösning för markörstödet är inte en implementerad funktion för SQL Server Parallel DataWarehousing TDS-fel