PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Séquences sans interruption PostgreSQL

Les séquences ont des espaces pour permettre des insertions simultanées. Tenter d'éviter les lacunes ou de réutiliser les identifiants supprimés crée d'horribles problèmes de performances. Consultez la FAQ du wiki PostgreSQL.

PostgreSQL SEQUENCE s sont utilisés pour allouer des identifiants. Ceux-ci ne font qu'augmenter, et ils sont exemptés des règles habituelles de restauration des transactions pour permettre à plusieurs transactions de saisir de nouveaux identifiants en même temps. Cela signifie que si une transaction est annulée, ces identifiants sont "jetés" ; il n'y a pas de liste d'identifiants "gratuits", juste le compteur d'identifiants actuel. Les séquences sont également généralement incrémentées si la base de données s'arrête de manière incorrecte.

Les clés synthétiques (ID) sont sans signification en tous cas. Leur ordre n'est pas significatif, leur seule propriété de signification est l'unicité. Vous ne pouvez pas mesurer de manière significative à quel point deux identifiants sont "éloignés", ni dire de manière significative si l'un est supérieur ou inférieur à l'autre. Tout ce que vous pouvez faire est de dire "égal" ou "différent". Tout le reste est dangereux. Vous ne devriez pas vous soucier des lacunes.

Si vous avez besoin d'une séquence sans interruption qui réutilise les identifiants supprimés, vous pouvez en avoir un, il vous suffit de renoncer à une énorme quantité de performances pour cela - en particulier, vous ne pouvez pas avoir de simultanéité sur INSERT s du tout, car vous devez parcourir la table pour trouver l'ID libre le plus bas, verrouiller la table en écriture afin qu'aucune autre transaction ne puisse revendiquer le même ID. Essayez de rechercher "postgresql gapless sequence".

L'approche la plus simple consiste à utiliser une table de compteurs et une fonction qui obtient le prochain ID. Voici une version généralisée qui utilise une table de compteur pour générer des ID sans interruption consécutifs ; cependant, il ne réutilise pas les identifiants.

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

Utilisation :

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

Notez que lorsqu'une transaction ouverte a obtenu un ID, toutes les autres transactions qui tentent d'appeler get_next_id bloquera jusqu'à ce que la 1ère transaction soit validée ou annulée. Ceci est inévitable et pour les ID sans espace et est par conception.

Si vous souhaitez stocker plusieurs compteurs à des fins différentes dans une table, ajoutez simplement un paramètre à la fonction ci-dessus, ajoutez une colonne à la table des compteurs et ajoutez un WHERE clause à la UPDATE qui correspond au paramètre à la colonne ajoutée. De cette façon, vous pouvez avoir plusieurs rangées de compteurs verrouillées indépendamment. Ne pas ajoutez simplement des colonnes supplémentaires pour les nouveaux compteurs.

Cette fonction ne réutilise pas les identifiants supprimés, elle évite simplement d'introduire des lacunes.

Pour réutiliser les identifiants, je conseille ... de ne pas réutiliser les identifiants.

Si vous le devez vraiment, vous pouvez le faire en ajoutant un ON INSERT OR UPDATE OR DELETE déclencheur sur la table d'intérêt qui ajoute les ID supprimés à une table secondaire de liste libre et les supprime de la table de liste libre lorsqu'ils sont INSERT éd. Traiter une UPDATE en tant que DELETE suivi d'un INSERT . Modifiez maintenant la fonction de génération d'ID ci-dessus afin qu'elle fasse un SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 et si trouvé, DELETE c'est cette rangée. IF NOT FOUND obtient un nouvel ID de la table du générateur comme d'habitude. Voici une extension non testée de la fonction précédente pour prendre en charge la réutilisation :

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;