JSONB oder echte Spalten: wo die Grenze verläuft
Ein Dokumentfeld kostet nichts, solange niemand darin sucht, und wird teuer in dem Moment, in dem ein Bericht darauf filtert und der Planer die Trefferzahl nicht mehr schätzen kann.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Aus dem flexiblen Zusatzfeld wird die halbe Tabelle
Der Anfang ist immer vernünftig. Ein Formular bekommt drei optionale Angaben, die je Mandant anders heißen. Statt drei Spalten anzulegen, die bei den meisten Datensätzen leer bleiben, entsteht eine Spalte attribute vom Typ jsonb. Das spart eine Migration und funktioniert.
Zwei Jahre später stehen dreißig Schlüssel darin. Der Bericht filtert nach attribute->>'region', die Fachabteilung möchte nach attribute->>'kostenstelle' gruppieren, und irgendwo in der Anwendung steht Code, der prüft, ob das Feld betrag eine Zahl oder eine Zeichenkette enthält, weil beides vorkommt. Genau dieser Prüfcode ist die Rechnung für die eingesparte Migration.
Technisch kommen zwei Effekte dazu. Der Planer schätzt die Trefferzahl bei Bedingungen auf Dokumentinhalte deutlich schlechter als bei normalen Spalten, weil ihm die Verteilung einzelner Schlüssel fehlt, und wählt darauf aufbauend ungünstige Verbindungsarten. Und jede Änderung an einem einzelnen Schlüssel schreibt das gesamte Dokument neu, weil eine Zeile immer nur als Ganzes neu geschrieben wird.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Dokument, Spalte und der Mittelweg dazwischen
JSONB-Spalte
Ein Dokument je Zeile, beliebige Struktur, keine Migration bei neuen Attributen, indizierbar über GIN oder über Ausdrücke.
Eigene Spalte
Typisiert, mit NOT NULL, CHECK und Fremdschlüssel, sauber geschätzt vom Planer, jede Erweiterung braucht eine Migration.
Dokument plus generierte Spalte
Das Dokument bleibt, die wichtigen Werte werden als gespeicherte generierte Spalten herausgezogen und wie echte Spalten behandelt.
| Entscheidungsfrage | JSONB-Spalte | Eigene Spalte | Dokument plus generierte Spalte |
|---|---|---|---|
| Wie gut lässt sich gezielt filtern? | Kommt darauf an Mit einem GIN-Index geht die Suche nach Enthaltensein schnell, für Bereichsabfragen und Sortierung fehlt die passende Struktur, und die Schätzungen bleiben grob. | Stärke Gleichheit, Bereiche, Sortierung und Verbindungen laufen über den passenden Index, und der Planer kennt die Verteilung aus der Statistik. | Stärke Der herausgezogene Wert verhält sich wie jede andere Spalte, inklusive Statistik und aller Indexarten. |
| Wie gut lässt sich Datenqualität erzwingen? | Schwäche Typ und Pflichtfeld gelten nicht je Schlüssel. Prüfungen musst du in die Anwendung legen oder über CHECK-Bedingungen auf einzelne Ausdrücke nachbilden. | Stärke Typ, NOT NULL, CHECK und Fremdschlüssel greifen unabhängig davon, welcher Weg die Daten schreibt. | Kommt darauf an Auf die generierte Spalte kannst du CHECK legen und einen Fremdschlüssel setzen, das darunterliegende Dokument bleibt aber weiter frei beschreibbar. |
| Was kostet eine neue Angabe im Modell? | Stärke Ein neuer Schlüssel braucht keine Migration und keine Absprache mit dem Betrieb. Genau deshalb entstehen solche Spalten überhaupt. | Kommt darauf an ALTER TABLE ADD COLUMN läuft in aktuellen Versionen sofort durch, es bleibt aber eine Migration, die geplant und ausgerollt werden muss. | Kommt darauf an Für einen neuen Schlüssel entsteht zunächst kein Aufwand. Sobald er wichtig wird, kommt eine generierte Spalte dazu, und das ist wieder eine Migration. |
| Wie verhält sich die Schreiblast? | Schwäche Jede Änderung schreibt das gesamte Dokument neu. Bei großen Dokumenten mit einem häufig geänderten Schlüssel entsteht spürbarer Zusatzaufwand und mehr aufzuräumender Müll. | Kommt darauf an Auch hier wird die ganze Zeile neu geschrieben, sie ist aber schmaler, und große Werte in getrennter Ablage bleiben unberührt. | Schwäche Die Schreiblast entspricht der des Dokuments, denn geändert wird weiterhin das Dokument. Der Gewinn liegt beim Lesen, nicht beim Schreiben. |
| Wie gut versteht der Planer die Daten? | Schwäche Für Bedingungen auf Dokumentinhalte gibt es keine Statistik je Schlüssel. Die Schätzungen sind grob, und daraus entstehen ungünstige Verbindungsreihenfolgen. | Stärke Häufigkeiten und Histogramme stehen zur Verfügung, die Schätzung ist belastbar und der Plan entsprechend stabil. | Stärke Für die generierte Spalte werden Statistiken wie für jede andere gesammelt, damit stimmen auch die Schätzungen. |
Wie gut lässt sich gezielt filtern?
Mit einem GIN-Index geht die Suche nach Enthaltensein schnell, für Bereichsabfragen und Sortierung fehlt die passende Struktur, und die Schätzungen bleiben grob.
Gleichheit, Bereiche, Sortierung und Verbindungen laufen über den passenden Index, und der Planer kennt die Verteilung aus der Statistik.
Der herausgezogene Wert verhält sich wie jede andere Spalte, inklusive Statistik und aller Indexarten.
Wie gut lässt sich Datenqualität erzwingen?
Typ und Pflichtfeld gelten nicht je Schlüssel. Prüfungen musst du in die Anwendung legen oder über CHECK-Bedingungen auf einzelne Ausdrücke nachbilden.
Typ, NOT NULL, CHECK und Fremdschlüssel greifen unabhängig davon, welcher Weg die Daten schreibt.
Auf die generierte Spalte kannst du CHECK legen und einen Fremdschlüssel setzen, das darunterliegende Dokument bleibt aber weiter frei beschreibbar.
Was kostet eine neue Angabe im Modell?
Ein neuer Schlüssel braucht keine Migration und keine Absprache mit dem Betrieb. Genau deshalb entstehen solche Spalten überhaupt.
ALTER TABLE ADD COLUMN läuft in aktuellen Versionen sofort durch, es bleibt aber eine Migration, die geplant und ausgerollt werden muss.
Für einen neuen Schlüssel entsteht zunächst kein Aufwand. Sobald er wichtig wird, kommt eine generierte Spalte dazu, und das ist wieder eine Migration.
Wie verhält sich die Schreiblast?
Jede Änderung schreibt das gesamte Dokument neu. Bei großen Dokumenten mit einem häufig geänderten Schlüssel entsteht spürbarer Zusatzaufwand und mehr aufzuräumender Müll.
Auch hier wird die ganze Zeile neu geschrieben, sie ist aber schmaler, und große Werte in getrennter Ablage bleiben unberührt.
Die Schreiblast entspricht der des Dokuments, denn geändert wird weiterhin das Dokument. Der Gewinn liegt beim Lesen, nicht beim Schreiben.
Wie gut versteht der Planer die Daten?
Für Bedingungen auf Dokumentinhalte gibt es keine Statistik je Schlüssel. Die Schätzungen sind grob, und daraus entstehen ungünstige Verbindungsreihenfolgen.
Häufigkeiten und Histogramme stehen zur Verfügung, die Schätzung ist belastbar und der Plan entsprechend stabil.
Für die generierte Spalte werden Statistiken wie für jede andere gesammelt, damit stimmen auch die Schätzungen.
Was passt wann
- Ein Attribut taucht in Berichten, Filtern oder Verknüpfungen auf.
- Eigene Spalte, mindestens aber eine gespeicherte generierte Spalte über dem Dokument.
- Jeder Mandant liefert andere Zusatzfelder, und sie werden fast nur zusammen mit dem Datensatz angezeigt.
- JSONB, mit einem GIN-Index nur dann, wenn tatsächlich darin gesucht wird.
- Das Modell steht noch nicht fest und die Anforderungen ändern sich wöchentlich.
- Mit JSONB starten, aber von Anfang an beobachten, welche Schlüssel in Abfragen auftauchen. Diese wandern nach und nach in Spalten.
Fünf Fragen je Attribut
- 01 Wird nach dem Attribut gefiltert, sortiert oder gruppiert?
- 02 Soll für das Attribut ein Typ oder eine Regel erzwungen werden?
- 03 Verweist es auf eine andere Tabelle, braucht also einen Fremdschlüssel?
- 04 Ändert es sich häufig, während das übrige Dokument groß ist?
- 05 Ist die Struktur je Datensatz wirklich unterschiedlich?
Was du nach dieser Seite entscheiden kannst
Die Frage lautet nicht, ob JSONB gut oder schlecht ist, sondern welche Rolle ein Attribut in der Anwendung spielt. Diese Rolle lässt sich für jeden Schlüssel in einem Satz beantworten, und daraus ergibt sich die Zuordnung fast von selbst.
Die Filterfrage stellen
Steht ein Attribut jemals in einer WHERE-Bedingung, in einem JOIN oder in einer Gruppierung, gehört es in eine eigene Spalte. Alles andere ist eine Wette darauf, dass der Planer schon nicht danebenschätzen wird.
Datenqualität einfordern
Eine echte Spalte kennt Typ, NOT NULL, CHECK und Fremdschlüssel. In JSONB ist jeder Wert erst einmal erlaubt, auch die Zahl als Zeichenkette und der Tippfehler im Schlüsselnamen. Die Prüfung wandert dann in die Anwendung, und zwar in jede.
Die Indexart bewusst wählen
Ein GIN-Index über die ganze Spalte bedient Enthaltensein und Schlüsselsuche, wird aber groß. Ein B-Tree über einen Ausdruck wie ((daten->>'region')) ist klein und schnell für Gleichheit auf genau diesem einen Schlüssel.
Schreiblast realistisch einschätzen
Wird ein einzelner Schlüssel häufig aktualisiert, während das restliche Dokument groß ist, schreibt die Datenbank bei jeder Änderung viele unnötige Bytes. Genau dieser Schlüssel gehört als Spalte heraus.
Generierte Spalten als Mittelweg nutzen
Eine gespeicherte generierte Spalte zieht einen Wert aus dem Dokument, ist typisiert, indizierbar und bleibt automatisch synchron. Du behältst das Dokument und bekommst für die wichtigen Attribute trotzdem echte Spalten.
json und jsonb auseinanderhalten
json speichert den Text unverändert samt Leerzeichen und doppelten Schlüsseln und muss bei jedem Zugriff neu geparst werden. jsonb speichert zerlegt, ist indizierbar und die richtige Wahl, sobald du mit den Daten arbeiten und sie nicht nur archivieren willst.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
json, jsonb und was der Unterschied im Alltag bedeutet
json speichert genau die Zeichenkette, die hereinkommt, inklusive Leerzeichen, Reihenfolge der Schlüssel und doppelt vorkommender Schlüssel, und wird bei jedem Zugriff neu geparst. jsonb speichert eine zerlegte Form: Die Reihenfolge geht verloren, doppelte Schlüssel werden auf den letzten reduziert, dafür ist der Zugriff auf einen Schlüssel günstig und die Spalte lässt sich indizieren.
Für Anwendungsdaten ist jsonb praktisch immer die richtige Wahl. json ist dann sinnvoll, wenn du eine eingegangene Nachricht unverändert aufbewahren musst, etwa für eine spätere Prüfung oder eine Signatur, und sie ohnehin nicht durchsuchst.
Die Zugriffsoperatoren verwechselt man am Anfang zuverlässig. -> liefert wieder jsonb, ->> liefert Text. Ein Vergleich wie daten->'menge' = '5' vergleicht deshalb JSON-Werte, während (daten->>'menge')::int = 5 das tut, was gemeint war. Für Pfade gibt es #> und #>>, und für komplexere Ausdrücke die SQL/JSON-Pfadsprache mit den Operatoren @? und @@.
Indizieren, ohne die Datenbank aufzublähen
Der voreingestellte GIN-Index über eine jsonb-Spalte legt Einträge für Schlüssel und Werte an. Er bedient sowohl den Enthaltensein-Operator @> als auch die Frage, ob ein Schlüssel existiert, und wird entsprechend groß. Die Variante jsonb_path_ops indiziert nur Kombinationen aus Pfad und Wert als Hashwerte, ist deutlich kleiner und für @> meist schneller, unterstützt dafür die Existenzfrage nicht.
In vielen Fällen brauchst du gar keinen GIN-Index. Wenn nur nach einem einzigen Schlüssel gefiltert wird, ist ein B-Tree über den Ausdruck die bessere Wahl: CREATE INDEX ON auftraege (((daten->>'region'))). Der Index bleibt klein, unterstützt Bereiche und Sortierung, und die Abfrage muss den Ausdruck exakt so schreiben, damit er verwendet wird.
Teilindizes passen gut dazu. Wird nur in den offenen Vorgängen gesucht, indiziere über eine WHERE-Bedingung am Index auch nur diese. Und beachte, dass ein Index über einen Ausdruck eigene Statistiken bekommt, was der Schätzung zusätzlich hilft.
Warum Schätzungen bei Dokumenten danebenliegen
Der Planer entscheidet anhand geschätzter Trefferzahlen, ob er einen Index nutzt und in welcher Reihenfolge er Tabellen verbindet. Für normale Spalten stützt er sich auf gesammelte Statistiken mit häufigsten Werten und Histogrammen. Für eine Bedingung auf einen Schlüssel innerhalb eines Dokuments gibt es diese Verteilung nicht, er arbeitet dann mit pauschalen Annahmen.
Die Folge sieht man erst bei Verbindungen. Schätzt der Planer 30 Treffer, wo tatsächlich 300.000 kommen, wählt er eine verschachtelte Schleife, und die Abfrage läuft statt in Millisekunden in Minuten. Genau deshalb ist ein häufig gefiltertes Attribut in einer eigenen Spalte nicht nur schöner, sondern schneller auf eine Weise, die sich durch keinen Index reparieren lässt.
Eine gespeicherte generierte Spalte löst das sauber, weil sie normal analysiert wird. Der Ausdruck muss dafür unveränderlich sein, ein Ausdruck wie ((daten->>'menge')::int) erfüllt das. Die Spalte bleibt automatisch synchron zum Dokument, du musst also keine Auslöser pflegen.
Der Umbau, wenn es schon zu spät ist
Wenn ein Dokumentfeld längst dreißig Schlüssel hat, hilft kein Grundsatzbeschluss, sondern eine Rangfolge. Wertest du das Abfrageprotokoll aus, siehst du, welche Schlüssel überhaupt in Bedingungen auftauchen, und meist sind es drei bis fünf. Genau diese ziehst du heraus, alles andere kann bleiben.
Der Weg ohne Ausfall geht über generierte Spalten. Du legst sie an, passt die Abfragen an, legst die Indizes darauf und lässt das Dokument unverändert. Wird die Anwendung später auch schreibend auf echte Spalten umgestellt, ist der Lesepfad schon fertig, und der Schlüssel kann in einem zweiten Schritt aus dem Dokument verschwinden.
Rechne beim Anlegen mit einer Umschreibung der Tabelle, denn eine gespeicherte generierte Spalte wird für jede vorhandene Zeile berechnet. Bei großen Tabellen gehört das in ein Wartungsfenster oder in ein Werkzeug, das die Tabelle im laufenden Betrieb neu aufbaut.
Dazu passende Kurse
Wie sich solche Entscheidungen im Modell auswirken, machen die PostgreSQL-Schulungen zu Datenmodellierung und JSONB an vorhandenen Datenbeständen sichtbar.
Die Abwägung zwischen Struktur und Freiheit stellt sich in jedem System, deshalb sind Kurse zur Datenmodellierung über Systemgrenzen hinweg auch für gemischte Landschaften interessant.
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Super Schulung, in der der Trainer auch auf eigene Fragestellungen eingegangen ist.
Super Grundlagenschulung mit allen wichtigen Punkten und hilfreichen Tipps!
Schön, sich 5 Tage ungestört mit Postgres beschäftigen zu können.
Häufige Fragen
Ist JSONB langsamer als eine normale Spalte?
Kann ich in JSONB Pflichtfelder erzwingen?
Wann lohnt sich ein GIN-Index wirklich?
Ersetzt JSONB eine dokumentorientierte Datenbank?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Datenbanken-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Modellentscheidungen, die zwei Jahre halten
Wo JSONB trägt und wo eine Spalte hingehört, arbeiten die PostgreSQL-Kurse bei cmt an Modellen durch, die schon gewachsen sind.