Sequenzen, Lücken und warum sie normal sind
AI generated
SELECT
JOIN
SQL / Sequenzen
Sequenzen, Lücken und warum sie normal sind
warum das Kämpfen gegen Lücken meist unnötiger Aufwand ist

Kaum ein Verhalten von Auto-Increment-Sequenzen verunsichert Entwickler so zuverlässig wie eine ID-Spalte mit fehlenden Werten. Der Reflex, das als Fehler zu behandeln und mit komplexer Zusatzlogik zu beheben, führt fast immer zu mehr Problemen als er löst: gesperrten Tabellen, blockierten Transaktionen und schlechterer Skalierbarkeit. Dieser Artikel erklärt, warum Lücken eine strukturell unvermeidbare Folge von Rollbacks, Sequence-Caching und paralleler Transaktionsausführung sind, warum das in der weit überwiegenden Zahl der Fälle vollkommen unproblematisch ist, und wo die seltenen, legitimen Ausnahmen liegen.

9 Min. Lesezeit Sequence-Caching Lückenlose Nummern

1. Die typische Beobachtung: fehlende IDs in einer Auto-Increment-Spalte

Ein Blick in eine Tabelle mit Auto-Increment-Primärschlüssel zeigt gelegentlich eine ID-Folge wie 1001, 1002, 1005, 1006, obwohl niemand aktiv Zeilen gelöscht hat. Die naheliegende Vermutung ist ein Bug in der Anwendung oder ein Fehler in der Datenbank. Tatsächlich handelt es sich um ein vollkommen normales, gut dokumentiertes Verhalten, das aus der Funktionsweise von Sequenzen und Transaktionen folgt, nicht aus einem Defekt.

Der zentrale Denkfehler liegt in der Annahme, eine Sequence sei eine transaktionale Ressource, die bei einem Rollback ebenfalls zurückgesetzt wird. Genau das Gegenteil ist der Fall, und dieser Unterschied erklärt praktisch jedes beobachtete Lücken-Phänomen.

2. Warum Sequenzen bewusst nicht-transaktional funktionieren

Eine Sequence vergibt Werte außerhalb des normalen Transaktionskontexts, damit mehrere parallele Transaktionen gleichzeitig neue, garantiert eindeutige Werte anfordern können, ohne sich gegenseitig zu blockieren. Würde die Vergabe eines Sequence-Werts erst mit dem Commit der aufrufenden Transaktion endgültig, müsste jede andere Transaktion, die ebenfalls einen neuen Wert braucht, warten, bis die erste Transaktion committet oder zurückrollt. Bei hoher Nebenläufigkeit würde das die Sequence zu einem harten Flaschenhals machen.

Weil die Vergabe stattdessen sofort und unabhängig vom weiteren Schicksal der Transaktion erfolgt, bleibt ein einmal vergebener Wert für immer verbraucht, selbst wenn die zugehörige Transaktion später zurückgerollt wird. Diese bewusste Design-Entscheidung tauscht garantierte Lückenlosigkeit gegen hohe Nebenläufigkeit, und für die überwältigende Mehrheit der Anwendungsfälle ist das ein sehr guter Tausch.


BEGIN;
INSERT INTO bestellung (kunde_id) VALUES (42); -- erhält z.B. id=1005
ROLLBACK; -- Transaktion wird verworfen

BEGIN;
INSERT INTO bestellung (kunde_id) VALUES (43); -- erhält id=1006, NICHT 1005
COMMIT;

3. Rollbacks als häufigste Ursache für Lücken im Alltag

Jede Transaktion, die einen neuen Sequence-Wert anfordert und anschließend aus fachlichen oder technischen Gründen zurückrollt, etwa weil eine nachgelagerte Validierung fehlschlägt, ein Constraint verletzt wird oder die Anwendung eine Exception wirft, hinterlässt eine dauerhafte Lücke. In produktiven Systemen mit Validierungslogik, optimistischem Sperren oder externen Abhängigkeiten innerhalb einer Transaktion ist ein gewisser Anteil fehlschlagender Transaktionen völlig normal.

Je höher die Rate fehlschlagender oder bewusst zurückgerollter Transaktionen in einem System ist, desto mehr Lücken entstehen. Das ist kein Qualitätsmerkmal der Datenbank, sondern eine direkte, erwartbare Konsequenz aus der Menge an Transaktionen, die aus gutem Grund nicht durchkommen.

4. Sequence-Caching als zweite, oft unterschätzte Ursache

Viele Datenbanksysteme cachen aus Performance-Gründen ganze Blöcke von Sequence-Werten pro Datenbankverbindung oder Session, statt bei jeder Anfrage einen einzelnen Wert mit entsprechendem Synchronisationsaufwand zu vergeben. Wird eine Verbindung beendet oder die Datenbank neu gestartet, bevor der gecachte Block vollständig verbraucht wurde, gehen die verbleibenden, bereits reservierten Werte dieses Blocks endgültig verloren.

Dieses Verhalten ist unabhängig von Transaktions-Rollbacks und tritt selbst bei Systemen mit einer Erfolgsquote von hundert Prozent auf, sobald Verbindungen regelmäßig neu aufgebaut werden, was in modernen Anwendungen mit Connection Pooling und Autoscaling der Regelfall ist. Wer Cache-Größe und Neustart-Häufigkeit kennt, kann die ungefähre Größenordnung der dadurch entstehenden Lücken grob abschätzen, sie aber nicht verhindern, ohne den Performance-Vorteil des Caching aufzugeben.


-- Sequence mit Caching von 20 Werten pro Session
CREATE SEQUENCE bestellung_id_seq
    START WITH 1000
    INCREMENT BY 1
    CACHE 20;
-- Bei Verbindungsabbruch nach nur 3 verbrauchten Werten
-- gehen die restlichen 17 reservierten Werte verloren

5. Parallele Transaktionen und die Reihenfolge von Commits

Auch ohne einen einzigen Rollback erzeugen parallele Transaktionen ein weiteres, subtileres Phänomen: Die Reihenfolge, in der Sequence-Werte vergeben werden, muss nicht mit der Reihenfolge übereinstimmen, in der die zugehörigen Transaktionen tatsächlich committen. Eine Transaktion, die zuerst einen Sequence-Wert anfordert, kann durch längere Verarbeitungszeit später committen als eine zweite Transaktion, die einen höheren Wert erhalten, aber schneller abgeschlossen hat.

Für Anwendungscode bedeutet das: Eine ID-Spalte darf niemals als verlässlicher Indikator für die tatsächliche zeitliche Reihenfolge von Commits verwendet werden, auch wenn keine einzige Lücke vorliegt. Für eine belastbare zeitliche Sortierung ist ein dedizierter Zeitstempel mit ausreichender Auflösung die korrekte Lösung, nicht der Rückschluss aus der ID.

6. Einen echten Bug von einer harmlosen, normalen Lücke unterscheiden

Nicht jede Lücke ist automatisch harmlos, weshalb es sinnvoll ist, eine normale Lücke von einem tatsächlichen Bug unterscheiden zu können. Eine normale Lücke korreliert mit einer plausiblen Ursache: einem protokollierten Validierungsfehler, einem bekannten Rollback, einem neu gestarteten Connection Pool oder einem Schub paralleler Schreibvorgänge rund um den betroffenen ID-Bereich. Zeigen Anwendungs- oder Transaktionslogs ein passendes Ereignis zum ungefähr selben Zeitpunkt, handelt es sich um erwartetes Verhalten, nicht um einen Defekt.

Verdächtig wird eine Lücke, wenn sie ohne jeden zugehörigen Rollback, Fehlerlog-Eintrag oder Verbindungsereignis auftritt, insbesondere wenn sie sich in einem festen, unplausiblen Muster wiederholt. In diesem Fall ist die wahrscheinlichere Erklärung ein Anwendungsfehler, der IDs außerhalb der Datenbank erzeugt und verwirft, etwa weil die Anwendungsschicht selbst einen Block von Sequence-Werten vorab abruft und nur einen Teil davon tatsächlich verwendet, wodurch genau das oben beschriebene Caching-Problem unter anderer Kontrolle erneut entsteht.

7. Warum das Kämpfen gegen Lücken meist unnötiger Aufwand ist

Der Versuch, Lücken zu vermeiden, etwa durch das explizite Sperren der gesamten Tabelle beim Vergeben einer neuen ID, serialisiert alle Schreibvorgänge auf diese eine Tabelle. Was zuvor parallel ablaufen konnte, wird dadurch zu einer strikt sequentiellen Warteschlange, mit spürbaren Auswirkungen auf Durchsatz und Latenz, sobald die Schreiblast über ein triviales Niveau hinausgeht.

In der überwältigenden Mehrheit der Anwendungsfälle hat eine Lücke in der ID-Spalte keinerlei fachliche Bedeutung. Der Primärschlüssel dient als technischer, eindeutiger Identifikator, nicht als fortlaufende, lückenlose Zählung von Geschäftsvorfällen. Wird diese Unterscheidung sauber getroffen, verschwindet das vermeintliche Problem, ohne dass irgendeine zusätzliche Logik nötig wäre.

8. Seltene, legitime Ausnahmen: wenn Lückenlosigkeit fachlich gefordert ist

Es gibt Fälle, in denen Lückenlosigkeit keine technische Vorliebe, sondern eine fachliche oder regulatorische Anforderung ist. Das prominenteste Beispiel sind Rechnungsnummern, für die in vielen Rechtsordnungen eine fortlaufende, lückenlose Nummerierung ohne Sprünge gesetzlich vorgeschrieben ist, um Manipulationen an der Buchführung nachweisbar auszuschließen. Ähnliche Anforderungen gelten mitunter für bestimmte Vertragsnummern oder amtliche Belegnummern.

In diesen Fällen ist die Antwort nicht, die primäre Auto-Increment-Sequence zu manipulieren oder ihr Caching zu deaktivieren, sondern die lückenlose Nummer als separates, fachliches Attribut zu behandeln, das unabhängig vom technischen Primärschlüssel verwaltet wird.

9. Wie man lückenlose Nummern sauber getrennt vom Primärschlüssel löst

Die robuste Lösung ist eine dedizierte Zählertabelle mit genau einer Zeile pro fachlichem Kontext, deren Wert innerhalb derselben Transaktion, die auch die Rechnung erzeugt, per SELECT ... FOR UPDATE gelesen und erhöht wird. Weil diese Erhöhung innerhalb der Transaktion erfolgt, wird sie bei einem Rollback ebenfalls zurückgenommen, anders als bei einer echten Sequence, wodurch Lückenlosigkeit garantiert bleibt.

Der bewusste Nachteil dieser Lösung ist eine strikte Serialisierung aller Vorgänge, die eine neue Nummer für denselben Kontext benötigen, weil die Zeile bis zum Commit gesperrt bleibt. Das ist für Rechnungsnummern mit vergleichsweise geringem Volumen pro Zeiteinheit fast immer akzeptabel und ein bewusst in Kauf genommener, klar begrenzter Trade-off, ganz anders als das pauschale Sperren einer stark frequentierten Haupttabelle.


CREATE TABLE rechnungsnummer_zaehler (
    kontext TEXT PRIMARY KEY,
    naechster_wert BIGINT NOT NULL
);

BEGIN;
SELECT naechster_wert FROM rechnungsnummer_zaehler
    WHERE kontext = 'DE-2026' FOR UPDATE;
UPDATE rechnungsnummer_zaehler
    SET naechster_wert = naechster_wert + 1
    WHERE kontext = 'DE-2026';
-- Rechnung mit dem gelesenen Wert innerhalb derselben Transaktion anlegen
COMMIT;
Ursache Lücke entsteht durch Vermeidbar ohne Zusatzaufwand Fachlich relevant
Transaktions-Rollback Sequence bleibt vergeben, Zeile verschwindet Nein Meist nicht
Sequence-Caching Verbindungsabbruch mit ungenutztem Block Nein, nur durch Caching-Verzicht Meist nicht
Parallele Transaktionen Commit-Reihenfolge weicht von ID-Reihenfolge ab Nein, strukturell bedingt Nur bei falscher Nutzung als Zeitangabe
Gesetzliche Rechnungsnummer Fachliche Anforderung an Lückenlosigkeit Ja, über separate Zählertabelle Ja, regulatorisch bindend

Mironsoft

Datenbank-Optimierung, Query-Tuning und Migrationen

SQL-Abfragen, die bei Wachstum immer langsamer werden?

Wir analysieren und optimieren SQL-Datenbanken unabhängig vom eingesetzten System, planen sichere Migrationen und Schema-Änderungen und bringen Teams Query-Optimierung praxisnah bei.

Query-Optimierung

Langsame Abfragen analysieren und mit Indizes und Explain-Plänen gezielt beschleunigen.

Migrations-Planung

Schema-Änderungen und Datenmigrationen sicher und ohne Downtime umsetzen.

Team-Schulung

SQL-Grundlagen und Performance-Denken praxisnah im Entwicklerteam verankern.

10. Zusammenfassung

Sequenz-Lücken verstehen

Kernursache

Sequenzen vergeben Werte bewusst außerhalb des Transaktionskontexts, damit parallele Transaktionen sich nicht gegenseitig blockieren. Ein Rollback nimmt einen vergebenen Wert deshalb nie zurück.

Weitere Ursachen

Sequence-Caching mit Verbindungsabbrüchen und die von der Commit-Reihenfolge unabhängige Wertvergabe erzeugen zusätzliche, ebenfalls normale Lücken.

Warum nicht bekämpfen

Das Erzwingen von Lückenlosigkeit serialisiert Schreibvorgänge und kostet Durchsatz, während eine Lücke in der ID-Spalte in den meisten Fällen keinerlei fachliche Bedeutung hat.

Legitime Ausnahme

Gesetzlich vorgeschriebene, lückenlose Nummern wie Rechnungsnummern gehören in eine separate, transaktional gesperrte Zählertabelle statt in den technischen Primärschlüssel.

11. FAQ: Sequenz-Lücken verstehen

1Warum entstehen Lücken in einer Auto-Increment-Spalte, obwohl niemand Zeilen löscht?
Weil Sequenzen bewusst außerhalb des Transaktionskontexts arbeiten. Ein einmal vergebener Wert bleibt auch dann verbraucht, wenn die zugehörige Transaktion später zurückrollt.
2Warum sind Sequenzen nicht-transaktional gestaltet?
Damit mehrere parallele Transaktionen gleichzeitig neue Werte anfordern können, ohne aufeinander warten zu müssen. Eine transaktionale Vergabe würde die Sequence zu einem harten Flaschenhals unter Last machen.
3Kann Sequence-Caching auch ohne Rollbacks Lücken erzeugen?
Ja, wenn eine Datenbankverbindung beendet oder die Datenbank neu gestartet wird, bevor ein gecachter Block von Sequence-Werten vollständig verbraucht wurde, gehen die verbleibenden Werte dieses Blocks endgültig verloren.
4Darf man aus der Reihenfolge von Sequence-Werten auf die Commit-Reihenfolge schließen?
Nein, bei parallelen Transaktionen kann eine Transaktion mit niedrigerem Sequence-Wert später committen als eine mit höherem Wert. Für zeitliche Sortierung ist ein dedizierter Zeitstempel notwendig.
5Warum ist explizites Sperren zur Lückenvermeidung meist eine schlechte Idee?
Weil es alle Schreibvorgänge auf die betroffene Tabelle serialisiert, was Durchsatz und Latenz spürbar verschlechtert, sobald die Schreiblast über ein triviales Niveau hinausgeht.
6Hat eine Lücke in der ID-Spalte normalerweise eine fachliche Bedeutung?
In den allermeisten Fällen nicht. Der Primärschlüssel dient als technischer, eindeutiger Identifikator, nicht als fortlaufende Zählung von Geschäftsvorfällen.
7Für welchen Anwendungsfall ist Lückenlosigkeit tatsächlich zwingend erforderlich?
Das häufigste Beispiel sind Rechnungsnummern, für die in vielen Rechtsordnungen gesetzlich eine fortlaufende, lückenlose Nummerierung ohne Sprünge vorgeschrieben ist.
8Wie löst man lückenlose Rechnungsnummern sauber?
Über eine dedizierte Zählertabelle mit einer Zeile pro fachlichem Kontext, deren Wert per SELECT FOR UPDATE innerhalb derselben Transaktion gelesen und erhöht wird, die auch die Rechnung erzeugt.
9Welchen Nachteil hat die Zählertabellen-Lösung für Rechnungsnummern?
Sie serialisiert alle Vorgänge, die eine neue Nummer für denselben Kontext benötigen, weil die Zeile bis zum Commit gesperrt bleibt. Bei geringem Volumen pro Zeiteinheit ist das meist unproblematisch.
10Sollte man die primäre Auto-Increment-Sequence für Rechnungsnummern manipulieren?
Nein, die primäre Sequence sollte für den technischen Primärschlüssel unangetastet bleiben. Lückenlose, fachlich relevante Nummern gehören in ein separates Attribut mit eigener Verwaltungslogik.