PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Lückenlose PostgreSQL-Sequenzen

Sequenzen haben Lücken, um gleichzeitige Einfügungen zu ermöglichen. Der Versuch, Lücken zu vermeiden oder gelöschte IDs wiederzuverwenden, führt zu schrecklichen Leistungsproblemen. Siehe PostgreSQL-Wiki-FAQ.

PostgreSQL SEQUENCE s werden zur Vergabe von IDs verwendet. Diese werden immer größer und sind von den üblichen Transaktions-Rollback-Regeln ausgenommen, um mehreren Transaktionen zu ermöglichen, gleichzeitig neue IDs zu erhalten. Dies bedeutet, dass diese IDs "weggeworfen" werden, wenn eine Transaktion zurückgesetzt wird. Es wird keine Liste mit "freien" IDs geführt, sondern nur der aktuelle ID-Zähler. Sequenzen werden normalerweise auch inkrementiert, wenn die Datenbank unsauber heruntergefahren wird.

Synthetische Schlüssel (IDs) sind bedeutungslos ohnehin. Ihre Reihenfolge ist nicht signifikant, ihre einzige signifikante Eigenschaft ist die Eindeutigkeit. Sie können nicht aussagekräftig messen, wie weit zwei IDs voneinander entfernt sind, und Sie können auch nicht aussagekräftig sagen, ob eine größer oder kleiner als eine andere ist. Sie können nur „gleich“ oder „ungleich“ sagen. Alles andere ist unsicher. Sie sollten sich nicht um Lücken kümmern.

Wenn Sie eine lückenlose Sequenz benötigen, die gelöschte IDs wiederverwendet, können Sie eine haben, Sie müssen nur eine Menge Leistung dafür aufgeben - insbesondere können Sie keine Parallelität bei INSERT haben s überhaupt nicht, da Sie die Tabelle nach der niedrigsten freien ID durchsuchen und die Tabelle für Schreibvorgänge sperren müssen, damit keine andere Transaktion dieselbe ID beanspruchen kann. Versuchen Sie, nach „postgresql gapless sequence“ zu suchen.

Der einfachste Ansatz besteht darin, eine Zählertabelle und eine Funktion zu verwenden, die die nächste ID erhält. Hier ist eine verallgemeinerte Version, die eine Zählertabelle verwendet, um aufeinanderfolgende lückenlose IDs zu generieren; IDs werden jedoch nicht wiederverwendet.

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

Verwendung:

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

Beachten Sie, dass, wenn eine offene Transaktion eine ID erhalten hat, alle anderen Transaktionen versuchen, get_next_id aufzurufen blockiert, bis die erste Transaktion festgeschrieben oder zurückgesetzt wird. Dies ist unvermeidbar und für lückenlose IDs vorgesehen.

Wenn Sie mehrere Zähler für verschiedene Zwecke in einer Tabelle speichern möchten, fügen Sie einfach einen Parameter zur obigen Funktion hinzu, fügen Sie eine Spalte zur Zählertabelle hinzu und fügen Sie ein WHERE hinzu -Klausel zum UPDATE die den Parameter der hinzugefügten Spalte anpasst. Auf diese Weise können Sie mehrere unabhängig gesperrte Zählerreihen haben. nicht Fügen Sie einfach zusätzliche Spalten für neue Zähler hinzu.

Diese Funktion verwendet gelöschte IDs nicht wieder, sondern vermeidet lediglich das Einfügen von Lücken.

Zur Wiederverwendung von IDs empfehle ich ... IDs nicht wiederzuverwenden.

Wenn Sie es wirklich müssen, können Sie dies tun, indem Sie einen ON INSERT OR UPDATE OR DELETE hinzufügen Trigger für die Tabelle von Interesse, der gelöschte IDs zu einer Free-List-Nebentabelle hinzufügt und sie aus der Free-List-Tabelle entfernt, wenn sie INSERT sind ed. Behandle ein UPDATE als DELETE gefolgt von einem INSERT . Ändern Sie nun die obige ID-Generierungsfunktion so, dass sie ein SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 ausführt und wenn gefunden, DELETE ist diese Reihe. IF NOT FOUND erhält wie gewohnt eine neue ID aus der Generatortabelle. Hier ist eine ungetestete Erweiterung der vorherigen Funktion zur Unterstützung der Wiederverwendung:

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;