PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Sequenze gapless di PostgreSQL

Le sequenze hanno delle lacune per consentire inserimenti simultanei. Il tentativo di evitare lacune o di riutilizzare gli ID eliminati crea orribili problemi di prestazioni. Consulta le domande frequenti sul wiki di PostgreSQL.

PostgreSQL SEQUENCE s sono usati per allocare gli ID. Questi aumentano sempre e solo e sono esenti dalle consuete regole di rollback delle transazioni per consentire a più transazioni di acquisire nuovi ID contemporaneamente. Ciò significa che se una transazione esegue il rollback, quegli ID vengono "buttati via"; non c'è un elenco di ID "gratuiti", solo il contatore di ID corrente. Le sequenze vengono in genere incrementate anche se il database si chiude in modo non pulito.

Le chiavi sintetiche (ID) sono prive di significato comunque. Il loro ordine non è significativo, la loro unica proprietà di significato è l'unicità. Non puoi misurare in modo significativo quanto siano "distanti" due ID, né puoi dire in modo significativo se uno è maggiore o minore di un altro. Tutto quello che puoi fare è dire "uguale" o "non uguale". Qualsiasi altra cosa non è sicura. Non dovresti preoccuparti delle lacune.

Se hai bisogno di una sequenza senza interruzioni che riutilizzi gli ID eliminati, puoi averne uno, devi solo rinunciare a un'enorme quantità di prestazioni per questo - in particolare, non puoi avere alcuna concorrenza su INSERT s del tutto, perché devi scansionare la tabella per l'ID libero più basso, bloccando la tabella per la scrittura in modo che nessun'altra transazione possa rivendicare lo stesso ID. Prova a cercare "sequenza gapless postgresql".

L'approccio più semplice consiste nell'utilizzare una tabella contatore e una funzione che ottiene l'ID successivo. Ecco una versione generalizzata che utilizza una tabella contatore per generare ID gapless consecutivi; non riutilizza gli ID, però.

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

Utilizzo:

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

Tieni presente che quando una transazione aperta ha ottenuto un ID, tutte le altre transazioni che tentano di chiamare get_next_id si bloccherà fino a quando la prima transazione non verrà confermata o ripristinata. Questo è inevitabile e per ID gapless ed è in base alla progettazione.

Se vuoi memorizzare più contatori per scopi diversi in una tabella, aggiungi semplicemente un parametro alla funzione sopra, aggiungi una colonna alla tabella dei contatori e aggiungi un WHERE clausola al UPDATE che corrisponde al parametro alla colonna aggiunta. In questo modo puoi avere più righe di contatori bloccate in modo indipendente. non aggiungi solo colonne extra per nuovi contatori.

Questa funzione non riutilizza gli ID cancellati, evita semplicemente di introdurre lacune.

Per riutilizzare gli ID consiglio... di non riutilizzare gli ID.

Se proprio devi, puoi farlo aggiungendo un ON INSERT OR UPDATE OR DELETE trigger sulla tabella di interesse che aggiunge gli ID eliminati a una tabella di lista libera e li rimuove dalla tabella di lista libera quando sono INSERT ed. Tratta un UPDATE come DELETE seguito da un INSERT . Ora modifica la funzione di generazione dell'ID sopra in modo che esegua un SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 e se trovato, DELETE è quella fila. IF NOT FOUND ottiene un nuovo ID dalla tabella del generatore come di consueto. Ecco un'estensione non testata della funzione precedente per supportare il riutilizzo:

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;