Menu

SQLite ROWID verstehen: INTEGER PRIMARY KEY & WITHOUT ROWID

Was ROWID in SQLite wirklich ist, wann INTEGER PRIMARY KEY daraus deine eigene Spalte macht und wofür WITHOUT ROWID-Tabellen gut sind.

Diese Seite enthält ausführbare Editoren — bearbeiten, ausführen und Ausgabe sofort sehen.

Jede Tabelle hat eine versteckte Spalte

Sobald du in SQLite eine Tabelle anlegst, bekommst du automatisch eine Spalte mit dazu, die du nie deklariert hast:

Diese rowid-Spalte ist tatsächlich real. SQLite vergibt sie automatisch für jede Zeile in jeder gewöhnlichen Tabelle – ob du sie haben willst oder nicht. Es handelt sich um einen vorzeichenbehafteten 64-Bit-Integer, der innerhalb der Tabelle eindeutig ist. Genau diesen Schlüssel nutzt SQLite intern, um Zeilen in seinem B-Tree-Speicher zu finden. Stell sie dir als das Rückgrat der Tabelle vor – der Index, der alles andere zusammenhält.

Normalerweise siehst du sie nicht, weil SELECT * sie nicht mitliefert. Du musst sie ausdrücklich beim Namen ansprechen.

Drei Aliase für ROWID

Da rowid in SQL-Code für andere Datenbanken so häufig vorkommt, akzeptiert SQLite gleich drei Namen für ein und dieselbe Spalte:

rowid, oid und _rowid_ zeigen alle auf dieselbe versteckte Spalte. Wenn du eine echte Spalte mit einem dieser Namen anlegst, gewinnt deine Spalte und der Alias steht nicht mehr zur Verfügung – das ist aber auch der einzige Haken. Im Alltag schreibst du einfach rowid.

INTEGER PRIMARY KEY ist das Zauberwort

Jetzt kommt der Teil, an dem fast jeder stolpert, der von anderen Datenbanken umsteigt. Wenn du eine Spalte exakt als INTEGER PRIMARY KEY deklarierst, wird diese Spalte nicht separat gespeichert – sie wird zum rowid:

rowid und id sind ein und dieselbe Spalte unter zwei Namen. Bei einem INSERT ohne id wird automatisch eine Ganzzahl vergeben (in der Regel max rowid + 1). Genau deshalb ist INTEGER PRIMARY KEY in SQLite der effizienteste Weg, einer Tabelle einen automatisch hochzählenden Schlüssel zu spendieren – keine zusätzliche Spalte, kein zusätzlicher Index, einfach nur die rowid selbst.

Achte dabei auf die exakte Schreibweise: INT PRIMARY KEY ist nicht dasselbe – INT und INTEGER verhalten sich hier unterschiedlich:

In Tabelle a sind id und rowid identisch. In Tabelle b ist id dagegen eine ganz normale Spalte und rowid bleibt die separate, versteckte Integer-Spalte. Schlimmer noch: b.id wird beim Insert nicht automatisch gefüllt – der Wert ist NULL, bis du ihn selbst setzt. Wenn du also das Alias-Verhalten willst, schreib immer INTEGER PRIMARY KEY aus – komplett, ohne Abkürzung.

Die ROWID nach einem Insert auslesen

Nach einem INSERT willst du meistens wissen, welche rowid gerade vergeben wurde – typischerweise, um einen abhängigen Datensatz daran zu hängen. Genau dafür gibt es in SQLite last_insert_rowid():

Die Funktion liefert die rowid des zuletzt erfolgreich eingefügten Datensatzes auf der aktuellen Verbindung zurück. Die meisten Datenbanktreiber stellen denselben Wert über cursor.lastrowid oder etwas Vergleichbares bereit. Eine weitere Möglichkeit ist die RETURNING-Klausel (dazu später mehr), mit der du die rowid direkt als Teil des Inserts zurückbekommst.

ROWIDs sind nicht dauerhaft

Solange eine Zeile existiert, bleibt ihre rowid stabil – ein lebenslanger Identifier ist sie aber nicht. Ein VACUUM kann die rowids neu vergeben, und wenn du eine Zeile löschst, kann ihre Nummer später bei einem neuen Insert wiederverwendet werden:

Beachte: Die neue Zeile kann je nach SQLite-Version und Situation die alte rowid wiederverwenden – muss aber nicht. Der entscheidende Punkt ist: Du kannst dich nicht darauf verlassen, dass eine rowid auf Dauer eindeutig bleibt. Wenn du einen Identifier brauchst, der DELETEs, VACUUMs und Exporte übersteht, deklarierst du am besten eine eigene INTEGER PRIMARY KEY-Spalte (damit ist der Wert fest an diese Zeile gebunden). Und falls du explizit monoton steigende Werte willst, die nie erneut vergeben werden, kommt zusätzlich das Schlüsselwort AUTOINCREMENT ins Spiel.

WITHOUT ROWID-Tabellen in SQLite

Manchmal ist die rowid einfach unnötiger Overhead – typischerweise dann, wenn dein eigentlicher Schlüssel gar keine Ganzzahl ist. Eine Städtetabelle mit dem Namen als Schlüssel landet sonst zwangsläufig bei zwei Strukturen: einmal dem rowid-B-Tree und zusätzlich einem separaten Index auf name, der den Primary Key absichert. Mit WITHOUT ROWID fasst du beides zu einer einzigen Struktur zusammen:

Damit wird name zum eigentlichen Speicherschlüssel. Suchen über name sparen sich eine Indirektionsebene, und die Tabelle wird kleiner. Was man dafür aufgibt:

  • Kein rowid, oid oder _rowid_ — diese Spalten existieren schlicht nicht.
  • last_insert_rowid() wird bei Inserts in diese Tabelle nicht aktualisiert.
  • Inkrementelles BLOB-I/O und einige Replikations-Features stehen nicht zur Verfügung.
  • Die Tabelle muss einen PRIMARY KEY deklariert haben.

WITHOUT ROWID ist eine gezielte Optimierung, kein Standardfall. Greif dazu, wenn der Primärschlüssel nicht-ganzzahlig ist und die Tabelle groß ist oder viele Schreibzugriffe hat. Für gewöhnliche Tabellen mit Integer-Schlüssel ist das normale Rowid-Layout bereits optimal.

Das mentale Modell

Auf das Wesentliche reduziert:

  • Jede gewöhnliche SQLite-Tabelle hat einen versteckten 64-Bit-Integer-Schlüssel namens rowid.
  • INTEGER PRIMARY KEY (exakt diese Schreibweise) macht deine Spalte zu einem Alias dafür.
  • Mit last_insert_rowid() liest du den gerade vergebenen Wert aus.
  • Rowids können nach Löschungen wiederverwendet und durch VACUUM neu nummeriert werden.
  • WITHOUT ROWID-Tabellen verzichten auf den versteckten Schlüssel und nutzen direkt deinen deklarierten Primärschlüssel — praktisch bei nicht-ganzzahligen Schlüsseln, aber man verliert ein paar Features.

In den meisten Fällen denkst du gar nicht über die rowid nach. Du deklarierst id INTEGER PRIMARY KEY, überlässt die Nummerierung SQLite und gehst weiter. Die Mechanik wird erst dann relevant, wenn du Speicher tunen willst, bestehende Schemas liest oder dich fragst, warum sich INT PRIMARY KEY anders verhält als INTEGER PRIMARY KEY.

Als Nächstes: NOT NULL und DEFAULT

Jetzt, wo die Identität einer Zeile geklärt ist, kommt die nächste Ebene: dafür sorgen, dass die übrigen Spalten sinnvolle Werte enthalten. NOT NULL und DEFAULT erledigen den Großteil dieser Arbeit — sie sind als Nächstes dran.

Häufig gestellte Fragen

Was ist ROWID in SQLite überhaupt?

Jede normale SQLite-Tabelle hat eine versteckte Spalte namens rowid – ein 64-Bit-Integer mit Vorzeichen, der jede Zeile eindeutig identifiziert. Intern nutzt SQLite diesen Wert als tatsächlichen Schlüssel im B-Tree. Auslesen kannst du ihn jederzeit mit SELECT rowid, * FROM t, auch wenn du ihn nie selbst angelegt hast.

Worin unterscheiden sich ROWID und PRIMARY KEY in SQLite?

Den rowid gibt es immer, einen Primary Key musst du selbst deklarieren. Spannend wird's bei INTEGER PRIMARY KEY: Diese Spalte wird nämlich zum Alias für rowid und nicht etwa als zweite Spalte gespeichert. Jeder andere Primärschlüssel – Text, zusammengesetzt oder auch nur INT PRIMARY KEY ohne das volle INTEGER – liegt zusätzlich neben dem rowid, ersetzt ihn aber nicht.

Was bewirkt WITHOUT ROWID in SQLite?

Mit WITHOUT ROWID sagst du SQLite: Lass den versteckten rowid weg und nimm meinen deklarierten PRIMARY KEY direkt als Speicherschlüssel. Bei Tabellen mit nicht-ganzzahligen Schlüsseln spart das Platz und macht Lookups schneller – allerdings fallen Features wie last_insert_rowid() und das inkrementelle BLOB-I/O weg. Also bewusst einsetzen, nicht als Standardlösung.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S