7 Punkte von GN⁺ 2024-11-13 | 2 Kommentare | Auf WhatsApp teilen
  • Die offizielle Postgres-Dokumentation ist hervorragend, aber das Postgres-17-PDF hat 3.200 Seiten, sodass es für Einsteiger schwierig ist, sich vor dem Praxiseinsatz allein aus der Doku Schema-Design, SQL-Verhalten und betriebliche Fallstricke vollständig anzueignen
  • Wenn es keinen besonderen Grund dagegen gibt, sollten Daten normalisiert werden; Denormalisierung mit redundanten Daten für bessere Leseleistung hat den Preis von Inkonsistenzen und komplexeren Schreibvorgängen
  • SQL-Schlüsselwörter unterscheiden nicht zwischen Groß- und Kleinschreibung, aber NULL ist eher „unbekannt“; wer es wie null in allgemeinen Programmiersprachen vergleicht, erhält oft unerwartete Ergebnisse
  • Schon wenn man psql mit Pager, \\x, .psqlrc, \\pset null, Autovervollständigung, Backslash-Befehlen und \\copy gut nutzt, werden Lesbarkeit der Ausgabe, Navigation und CSV-Export deutlich angenehmer
  • Indizes, Locks, Transaktionen und JSONB sind mächtig, aber ohne Verständnis für Abfragepläne und betriebliche Einschränkungen kann es zu Performance-Einbrüchen oder Verfügbarkeitsproblemen kommen

Kontext, den man vor der riesigen offiziellen Dokumentation kennen sollte

  • Die offizielle Postgres-Dokumentation umfasst in der aktuellen Version 17 als US-Letter-PDF 3.200 Seiten, als A4-PDF sind es 3.024 Seiten
  • Es gibt viel praxisrelevantes Wissen, das man vor dem Einsatz von Postgres kennen sollte; manches gilt auch für andere SQL-DBMS, aber der genaue Geltungsbereich ist nicht immer eindeutig

Daten grundsätzlich normalisieren

  • Normalisierung ist der Prozess, doppelte oder unnötige Daten aus einem Datenbankschema zu entfernen
  • Wenn man in der Tabelle documents die user_email direkt speichert, muss man beim Ändern der E-Mail eines Nutzers alle Dokumentzeilen dieses Nutzers aktualisieren
    • Stattdessen kann jede Zeile in documents per Fremdschlüssel user_id auf eine Zeile in einer anderen Tabelle wie users verweisen
  • Es ist nicht nötig, jede Normalform wie „1st normal form“ auswendig zu kennen, aber der übliche Normalisierungsprozess führt oft zu einem wartungsfreundlicheren Schema
  • Denormalisierung bedeutet, redundante Daten vorzuhalten, um bestimmte Werte schnell lesen zu können, statt sie jedes Mal neu zu berechnen
    • In einer App für Mitarbeiterschichten könnte man die kumulierte Arbeitszeit des laufenden Jahres periodisch oder bei Änderungen an Arbeitszeiten berechnen und speichern, statt sie jedes Mal durch Summieren aller shift duration zu ermitteln
    • Diese Daten können innerhalb von Postgres liegen oder in einer Cache-Schicht wie Redis
  • Denormalisierung hat fast immer Kosten; typische Kosten sind mögliche Dateninkonsistenzen und höhere Schreibkomplexität

Die „Don’t do this“-Ratschläge des Postgres-Projekts

  • Im offiziellen Postgres-Wiki gibt es eine Liste „Don’t do this“
  • Es ist in Ordnung, wenn man nicht jeden Punkt versteht; Punkte, die man nicht versteht, wird man wahrscheinlich auch seltener falsch anwenden
  • Besonders die folgenden Hinweise sollte man sich merken

Leicht misszuverstehendes Verhalten in SQL

  • SQL-Schlüsselwörter müssen nicht großgeschrieben werden

    • SQL-Schlüsselwörter sind nicht case-sensitiv
    • Die folgenden Abfragen bedeuten dasselbe
    SELECT * FROM my_table WHERE x = 1 AND y > 2 LIMIT 10;
    select * from my_table where x = 1 and y > 2 limit 10;
    SELECT * from my_table WHERE x = 1 and y > 2 LIMIT 10;
    
    • Diese Eigenschaft ist nicht auf Postgres beschränkt
  • NULL unterscheidet sich von null/nil in allgemeinen Programmiersprachen

    • NULL in SQL entspricht eher „unbekannt“ als null oder nil in allgemeinen Programmiersprachen
    • NULL = NULL ergibt nicht true, sondern NULL
    • Vergleiche, bei denen eine Seite NULL ist, ergeben meist ebenfalls NULL
    • Für Vergleiche mit NULL sollte man folgende Operationen verwenden
      • x IS NULL: true, wenn x NULL ist
      • x IS NOT NULL: true, wenn x nicht NULL ist
      • x IS NOT DISTINCT FROM y: ähnlich wie x = y, behandelt NULL aber wie einen normalen Wert
      • x IS DISTINCT FROM y: ähnlich wie x != y/x <> y, behandelt NULL aber wie einen normalen Wert
    • Eine WHERE-Klausel gibt nur Zeilen zurück, wenn die Bedingung true ist
      • SELECT * FROM users WHERE title != 'manager' gibt keine Zeilen zurück, in denen title NULL ist
      • Denn NULL != 'manager' ergibt NULL
    • COALESCE gibt unter mehreren Argumenten den ersten Wert zurück, der nicht NULL ist
    COALESCE(NULL, 5, 10) = 5
    COALESCE(2, NULL, 9) = 2
    COALESCE(NULL, NULL) IS NULL
    

psql nützlicher verwenden

  • Die Lesbarkeit der Ausgabe verbessern

    • Wenn die Ausgabe beim Abfragen von Tabellen mit vielen Spalten oder langen Werten schwer lesbar ist, ist möglicherweise der Pager deaktiviert
    • Der Pager im Terminal erlaubt es, durch große Textmengen oder psql-Tabellen im Viewport zu scrollen
    • Bei Tabellen mit vielen Spalten kann man mit \\pset expanded oder \\x den expanded mode aktivieren
    • Wenn man das als Standard möchte, kann man \\x in ~/.psqlrc im Home-Verzeichnis eintragen
  • NULL in der Ausgabe klarer kennzeichnen

    • Die Standardeinstellung zeigt nicht eindeutig an, ob ein Wert NULL ist
    • In psql kann man eine Zeichenkette für die Anzeige von NULL festlegen
    \pset null '[NULL]'
    
    • Auch Unicode-Zeichenketten sind möglich; wer das als Standard möchte, kann denselben Befehl in ~/.psqlrc eintragen
  • Autovervollständigung und Backslash-Befehle nutzen

    • psql unterstützt Autovervollständigung wie eine interaktive Konsole
    • Gibt man einen Teil eines Schlüsselworts oder Tabellennamens ein und drückt Tab, kann der Rest ergänzt werden
    • Nützliche Backslash-Befehle sind unter anderem
      • \\?: Liste aller Shortcuts
      • \\d: zeigt Relations, also Tabellen und Sequenzen, sowie deren Eigentümer
      • \\d+: wie \\d, aber zusätzlich mit Größe und einigen Metadaten
      • \\d table_name: zeigt Tabellenschema, Spaltentypen, Nullable-Status, Default-Werte, Indizes und Fremdschlüssel-Constraints
      • \\e: bearbeitet die Abfrage im Standardeditor aus der Umgebungsvariable $EDITOR
      • \\h SQL_KEYWORD: zeigt Syntax und Dokumentationslink zum jeweiligen SQL-Schlüsselwort
  • CSV exportieren und SELECT-Aliase verwenden

    • Mit \\copy kann man Abfrageergebnisse als CSV speichern
    \copy (select * from some_table) to 'my_file.csv' CSV
    
    • Um die Spaltennamen in die erste Zeile aufzunehmen, fügt man die Option HEADER hinzu
    \copy (select * from some_table) to 'my_file.csv' CSV HEADER
    
    • \\copy vermeidet die erhöhten Rechte, die das standardnähere COPY-Statement benötigt
    • Ausgabespalten in SELECT können mit AS einen Alias erhalten
    SELECT vendor, COUNT(*) AS number_of_backpacks
    FROM backpacks
    GROUP BY vendor
    ORDER BY number_of_backpacks DESC;
    
    • In GROUP BY und ORDER BY kann man auf die Positionsnummern der nach SELECT aufgeführten Spalten verweisen
    SELECT vendor, COUNT(*) AS number_of_backpacks
    FROM backpacks
    GROUP BY 1
    ORDER BY 2 DESC;
    
    • Diese Kurzform ist praktisch, sollte aber besser nicht in Queries landen, die in Produktion ausgerollt werden

Indizes werden nicht immer verwendet, nur weil man sie anlegt

  • Indizes und Abfragepläne

    • Ein Index ist eine Datenstruktur, die als Abkürzungsverzeichnis dient, um Tabellenzeilen anhand bestimmter Felder zu finden
    • Der häufigste Indextyp ist der B-tree; er funktioniert für exakte Gleichheitsbedingungen wie WHERE a = 3 und Bereichsbedingungen wie WHERE a > 5
    • Man kann Postgres nicht direkt anweisen, einen bestimmten Index zu verwenden
    • Postgres sagt anhand der für jede Tabelle gepflegten Statistiken voraus, ob ein Index schneller ist als ein sequential scan über die gesamte Tabelle
    • Wenn man vor SELECT ... FROM ... ein EXPLAIN setzt, sieht man den Abfrageplan, also wie Postgres die Query voraussichtlich ausführt
    • Zum Lesen von Abfrageplänen können thoughtbots EXPLAIN-ANALYZE-Leitfaden, die pganalyze-Dokumentation, die offizielle Dokumentation und explain.depesz.com hilfreich sein
  • Kleine Tabellen und mehrspaltige Indizes

    • Bei Tabellen mit wenigen Zeilen, etwa in einer lokalen Entwicklungsdatenbank, bringt ein Index unter Umständen kaum Vorteile
    • Bei etwa 100 Zeilen kann Postgres entscheiden, dass ein sequential scan schneller ist als ein Index
    • Postgres unterstützt mehrspaltige Indizes
    CREATE INDEX CONCURRENTLY ON tbl (a, b);
    
    • Bedingungen wie WHERE a = 1 AND b = 2 können schneller sein als mit getrennten Indizes auf a und b
    • Der Grund ist, dass sich die Suchbedingungen beim Traversieren eines einzelnen B-tree effizient kombinieren lassen
    • Ein Index auf (a, b) kann auch Abfragen, die nur nach a filtern, ungefähr so schnell machen wie ein eigener Index nur auf a
    • Abfragen wie WHERE b = 5 können zwar ebenfalls schneller werden, sind aber unter Umständen nicht optimal
      • Da der Index zuerst nach a und dann nach b sortiert ist, muss er über alle a-Werte gehen, um die b-Werte zu finden
    • Wenn man über mehrere Spaltenkombinationen abfragt, legt man oft sowohl (a, b) als auch einen eigenen Index auf b an
    • Je nach Bedarf kann man sich auch auf getrennte Einzelindizes für a und b verlassen
  • Für prefix match text_pattern_ops verwenden

    • Bei einem materialized-path-Ansatz zur Speicherung hierarchischer Verzeichnisse möchte man möglicherweise alle Descendants finden, die mit einem bestimmten Prefix beginnen
    SELECT * FROM directories WHERE path LIKE '/1/2/3/%'
    
    • Selbst wenn man auf der Spalte path einen normalen B-tree-Index anlegt, wird dieser für diese Abfrage eventuell nicht verwendet
    CREATE INDEX CONCURRENTLY ON directories (path);
    
    • Damit die zeichenweise Sortierung für prefix match oder pattern match möglich wird, muss man eine operator class angeben
    CREATE INDEX CONCURRENTLY ON directories (path text_pattern_ops);
    

Betriebliche Probleme durch Locks und Transaktionen

  • Locks in Postgres

    • Ein Lock oder Mutex ist ein Mechanismus, der riskante Operationen jeweils nur einem Client gleichzeitig erlaubt
    • Aktualisierungen von Objekten wie Rows, Tabellen oder Views in einer Datenbank müssen entweder vollständig gelingen oder vollständig scheitern; um Situationen zu vermeiden, in denen durch gleichzeitige Operationen nur ein Teil erfolgreich ist, werden Locks auf den betroffenen Objekten genommen
    • Die Table-Lock-Stufen in Postgres reichen von weniger restriktiv bis stärker restriktiv
      • ACCESS SHARE: SELECT
      • ROW SHARE: SELECT ... FOR UPDATE
      • ROW EXCLUSIVE: UPDATE, DELETE, INSERT
      • SHARE UPDATE EXCLUSIVE: CREATE INDEX CONCURRENTLY
      • SHARE: CREATE INDEX, also ohne CONCURRENTLY
      • ACCESS EXCLUSIVE: viele Formen von ALTER TABLE, ALTER INDEX
    • Auf derselben Tabelle sind die folgenden Kombinationen möglich oder müssen warten
      • UPDATE während SELECT: möglich
      • UPDATE während CREATE INDEX CONCURRENTLY: möglich
      • SELECT während CREATE INDEX: möglich
      • SELECT während ALTER TABLE: wartet normalerweise
      • ALTER TABLE während SELECT: wartet normalerweise
    • Einige Formen von ALTER TABLE benötigen schwächere Locks; vollständige Informationen gibt es in der offiziellen Dokumentation zu expliziten Locks und im Leitfaden zu Lock-Konflikten nach Operation
  • Langsame ALTER TABLE und Lock-Warteschlangen

    • Wenn ALTER TABLE lange dauert, können selbst SELECT-Abfragen auf dieselbe Tabelle blockiert werden
    • Handelt es sich um eine zentrale Tabelle wie users, auf die alle Requests einer Web-App zugreifen, können Anfragen warten, in ein Timeout laufen und 503 zurückgeben
    • Häufige Gründe für langsame ALTER TABLE-Operationen sind
      • Hinzufügen einer Spalte mit nicht-konstantem Default
      • Ändern des Spaltentyps
      • Hinzufügen einer Uniqueness-Constraint
    • Seit Postgres 11 wurde das Problem behoben, dass beim Hinzufügen einer Spalte jeder Default langsam macht; problematisch kann weiterhin ein nicht-konstanter Default sein
    • Selbst wenn ALTER TABLE selbst eine schnelle Operation wäre, läuft es nicht, bevor der nötige Lock erworben wurde
      • Wenn gerade ein langsames SELECT aus einem alten internen Dashboard läuft, muss ALTER TABLE warten
    • Da Postgres-Locks eine Warteschlange bilden, können nachfolgende Queries auf dieselbe Tabelle, die hinter einem wartenden ALTER TABLE eintreffen, ebenfalls warten
    • Dasselbe Szenario wird auch in Migrations and exclusive locks näher erläutert
  • Auch lange Transaktionen sind riskant

    • Eine Transaktion bündelt mehrere Datenbank-Statements nach dem Alles-oder-nichts-Prinzip; sie beginnt mit BEGIN und endet mit COMMIT
    • Änderungen innerhalb einer Transaktion sind für andere Clients nicht sichtbar und werden erst mit COMMIT veröffentlicht
    • Das eignet sich für Vorgänge wie Überweisungen, bei denen die Belastung eines Kontos und die Gutschrift auf einem anderen zusammen erfolgreich sein oder zusammen zurückgerollt werden müssen
    • Wenn eine Transaktion einen Lock erwirbt, hält sie ihn bis zum COMMIT
    • Wenn man nach BEGIN eine bestimmte Row mit UPDATE ändert und dann den Platz verlässt, bleibt ein DELETE dieser Row durch einen anderen Client blockiert, bis die Transaktion committet wird
    • Transaktionen, die länger offen bleiben als nötig, können Abfragen oder Updates anderer Clients blockieren

JSONB ist ein scharfes Werkzeug

  • Performance- und Schema-Probleme bei JSONB

    • JSONB ist flexibel, hat aber bei falschem Einsatz erhebliche Nachteile
    • Postgres verfolgt keine Statistiken über JSONB-Spalten; daher können Gleichheitsabfragen auf einer einzelnen JSONB-Spalte viel langsamer sein als Abfragen auf einem normalen Satz von Spalten
    • Ein Beispiel dafür findet sich hier: 2000-fache Verlangsamung durch JSONB
    • In eine JSONB-Spalte passt praktisch alles; das ist mächtig, bietet aber wenig Garantien für die Struktur
    • Bei normalen Tabellen kann man anhand des Schemas das Ergebnis einer Abfrage eher vorhersagen; bei JSONB ist oft nicht klar, ob Key-Namen camelCase oder snake_case verwenden oder ob ein Status ein Boolean oder ein Enum ist
    • Die statische Typisierung gewöhnlicher Postgres-Daten gilt für JSONB nicht in gleicher Weise
  • Die Sperrigkeit von Typvergleichen mit JSONB

    • Wenn man in der JSONB-Spalte data der Tabelle backpacks Zeilen finden möchte, deren Feld brand den Wert JanSport hat, funktioniert die folgende Query nicht
    select * from backpacks where data['brand'] = 'JanSport';
    
    • Postgres erwartet, dass der Typ auf der rechten Seite des Vergleichs zum linken Typ passt; rechts muss also ein gültiges JSON-Dokument stehen
    • Ein JSON-Dokument muss ein Objekt, Array, String, eine Zahl, ein Boolean oder null sein; JanSport allein ist daher kein gültiges JSON
    • Korrekt ist entweder ein Vergleich mit einem JSON-String oder die Umwandlung der linken Seite in Postgres-text
    select * from backpacks where data['brand'] = '"JanSport"';
    
    select * from backpacks where data['brand'] = '"JanSport"'::jsonb;
    
    select * from backpacks where data->>'brand' = 'JanSport';
    
    • SQL-NULL und JSONB-null verhalten sich unterschiedlich
      • 'null'::jsonb = 'null'::jsonb ergibt true, aber NULL = NULL ergibt NULL
    • Für JSONB gibt es viele eigene Operatoren und Funktionen, die man sich kaum alle auf einmal merken kann
    • In Postgres gibt es sowohl JSON, das JSON-Werte als Text speichert, als auch JSONB, das sie in ein effizientes Binärformat umwandelt
    • JSONB hat Vorteile wie Indexierbarkeit; das JSON-Format kann man als Sonderfall betrachten

2 Kommentare

 
bbulbum 2024-11-19

Das sollte man nicht tun. Das werde ich mir irgendwann einmal durchlesen.

 
GN⁺ 2024-11-13
Meinungen auf Hacker News
  • PostgreSQL ist im Großen und Ganzen case-sensitive, aber SQL-Schlüsselwörter in Großbuchstaben zu schreiben, ist normalerweise der Versuch, die Lesbarkeit durch visuelles Pattern Matching zu erhöhen.
    Es ist nicht zwingend nötig, aber wenn ich Queries anderer Leute debuggen müsste, würde ich sie wohl durch einen Prettifier laufen lassen, um die Definitionen schnell zu überfliegen, ohne an kleinen syntaktischen Formfragen hängen zu bleiben.
    Wie beim Formatieren von Code in anderen Sprachen reduziert eine visuelle Struktur wie konsistente Einrückung die Zeit, die man für das Verständnis offensichtlicher Teile braucht, und hilft, sich auf das Wichtige zu konzentrieren.
    Allerdings hasse ich es wirklich, wenn in Bezeichnern tatsächlich Groß- und Kleinschreibung gemischt wird, wie bei actuallyUsingCaseInIdentifiers, und ich möchte keine Spalten sehen, die doppelte Anführungszeichen brauchen, nur damit man sie in der CLI ansehen kann.

    • Bezeichner in Großbuchstaben sehen wie austauschbare Blöcke aus und verlangsamen das Lesen im Vergleich zur Wortform, die Kleinbuchstaben haben.
    • Wenn man interaktiv mit SQL arbeitet, ist es ziemlich nützlich, diese Unterscheidung zu kennen.
      Wenn ich eine temporäre Query schnell tippe und wegwerfe, die niemand sehen wird, achte ich nicht auf Groß-/Kleinschreibung; SQL, das ins Repository committed wird, schreibt Befehle aber in ALL CAPS.
    • Ich verstehe Großbuchstaben als eine Art Syntax-Highlighting auf Schwarz-Weiß-Bildschirmen.
      Heute mit Farbe ist das nicht mehr nötig, aber das ist eine alte Erinnerung, und Belege habe ich keine.
    • PostgreSQL faltet Bezeichner zu Kleinbuchstaben, während der Standard sie zu Großbuchstaben faltet; bei der Behandlung von Groß-/Kleinschreibung weicht es also vom Standard ab.
      Trotzdem sollte man gequotete und ungequotete Bezeichner nicht mischen, und Abfragen interner Strukturen sind meist ohnehin nicht standardisiert, daher ist das nicht besonders relevant.
    • Mich würden Empfehlungen für Prettifier oder Linter für SQL interessieren.
  • Den „don’t do this“-Abschnitt im PostgreSQL-Wiki habe ich zum ersten Mal gesehen, und er ist ziemlich nützlich: https://wiki.postgresql.org/wiki/Don%27t_Do_This

    • Wenn diese Funktionen so einfache Fallen sind, frage ich mich, warum man sie nicht als deprecated markiert.
      Zum Beispiel scheint es sinnvoll, Funktionen wie Tabellenvererbung in neuen Schemas zu deaktivieren und absichtlich eine umständliche Konfiguration zu verlangen, um sie wieder einzuschalten.
    • Das erinnert an SQL Anti-patterns; ich halte es für ein Buch, das jeder lesen sollte, der mit Datenbanken arbeitet.
    • Es bringt mich dazu, einige Gewohnheiten zu überdenken, die ich aus der MySQL-Welt gelernt habe.
  • Vieles von dem, was hier genannt wird, gilt nicht nur für PostgreSQL.
    Dazu gehören das seltsame Verhalten von NULL und die Reihenfolge von Indexspalten; besonders die Wechselwirkung von NULL mit Indizes/Unique-Constraints ist auch in MySQL nicht intuitiv.
    Wenn man zum Beispiel in einer Benutzertabelle email als NOT NULL und username als NULL erlaubt hat und einen Unique-Constraint auf (email, username) setzt, kann man dieselbe email mehrfach mit username = NULL einfügen. Denn NULL ist nicht gleich einem anderen NULL.

    • Zur Info: Seit PostgreSQL 15 kann man dieses Verhalten bei Constraints und Unique-Indizes mit NULLS [NOT] DISTINCT beeinflussen.
      https://www.postgresql.org/docs/devel/sql-createtable.html#S...
    • Ich halte diesen Default praktisch für in Ordnung.
      Anwendungsfälle, die das gegenteilige Verhalten brauchen, sind deutlich seltener.
  • Man sollte es nicht einfach bei einer Aussage wie „Normalisiere deine Daten, wenn es keinen guten Grund dagegen gibt“ belassen.
    Auch auf der vom Autor verlinkten Seite werden einschließlich der unnormalisierten Form 11 Normalformen genannt; die meisten wissen nicht einmal, was diese sind, und 7 davon wird man nie verwenden.
    Man sollte niemanden dazu bringen, nach immer höheren Normalformen zu suchen.

    • Trotzdem hat der Autor meist einen Absatz hinzugefügt, der erklärt, was gemeint ist, und die Richtung halte ich für richtig.
      Auch in einem Projekt, in das ich kürzlich gewechselt bin, musste ich einige solcher Probleme beheben; es gibt kaum Gründe, Daten zu duplizieren.
    • Wenn dieser Artikel sich an Anfänger richtet, ist die Antwort bei Unsicherheit fast immer die dritte Normalform.
    • Die allgemeine Regel lautet: so weit wie möglich normalisieren und dann so lange denormalisieren, bis die nötige Performance erreicht ist.
  • Der erste Tipp lautet, täglich ein VACUUM auszuführen.
    Als wir anfingen, wusste ich das nicht und habe auf der reddit-Datenbank überhaupt kein VACUUM ausgeführt; als wir es eines Tages zwangsläufig laufen lassen mussten, war reddit fast einen ganzen Tag down, während wir darauf warteten, dass es fertig wurde.

    • Offenbar gab es kein autovacuum.
      Bei der Größe von reddit ist es erstaunlich, dass nicht zuerst die Transaction IDs ausgegangen sind.
  • Ich wünschte, Entwickler würden sich mehr um Normalisierung kümmern und damit aufhören, alles in JSONB-Spalten zu stopfen.

    • Schon lange bevor Datenbanken strukturiertes JSON speichern konnten, führten Junior-Entwickler heftige theoretische Debatten über den richtigen Grad an Normalisierung.
      Erfahrenere Entwickler wussten, dass die richtige Antwort lautet: nichts außer Schlüsseln duplizieren und nur äußerst widerwillig denormalisieren.
      Dann kamen Datenbanken wie Mongo auf, die etwas „Datenbankähnliches“ boten, bei dem Normalisierung schwierig oder sinnlos war, und bestärkten diese Juniors; das Ergebnis war, dass schreckliches Datenbankdesign und nicht wartbare Mülltürme kurzzeitig florierten.
      Inzwischen ist das Pendel zurückgeschwungen und die Vorteile normalisierter Datenbanken werden wiederentdeckt, aber JSON-Spalten bleiben ein Schlupfloch, in dem schlechte Praktiken gedeihen können.
    • Es gibt zwei Gründe, JSONB-Spalten zu verwenden.
      Erstens: um JSON zu speichern. Wenn ein Webserver eine Third-Party-API aufruft und die ursprüngliche API-Antwort in einer JSONB-Spalte speichert, bevor sie dort weiterverarbeitet wird, bleibt ein auditierbarer Verlauf erhalten, wenn man Probleme debuggen muss, die von dieser API stammen.
      Zweitens: um Sum Types zu speichern. Dass SQL keine Sum Types unterstützt, kann man als einen der größten Mängel beim Modellieren von Daten in SQL-Datenbanken betrachten.
      Es gibt verschiedene Workarounds, und „einfach in eine JSONB-Spalte packen und in der Anwendung validieren“ ist einer davon, aber keiner dieser Workarounds ist besonders gut.
    • Selbst wenn man auf Normalisierung achtet, entsteht am Ende oft trotzdem eine Krimskrams-JSONB-Schublade.
      Solange man Werte aus JSONB nicht eigentlich in eigene Spalten hochziehen sollte und stattdessen schlechte Queries darin schreibt, sehe ich das an sich nicht als großes Problem.
    • Die meisten Entwickler, die solche Tools heute verwenden, bauen im Grunde ihr eigenes Datenbankmanagementsystem und überlassen einem anderen DBMS nur die Persistenz.
      Denn wenn die Anforderungen an Persistenz erfolgreich erfüllt sind, gibt es keinen starken Druck, über gutes Design nachzudenken.
      Ob es sinnvoll ist, ein DBMS auf einem DBMS zu bauen, ist fraglich, aber jedenfalls ist das der aktuelle Zustand.
    • Damit dieser Ansatz richtig funktioniert, braucht man einen Schema-Migrationsprozess, einschließlich der Fähigkeit, Schemaänderungen zurückzurollen.
      Wenn eine neue Spalte die Performance ruiniert oder Probleme verursacht, muss man sie zurücknehmen können.
      Wenn CLI-Tools beteiligt sind, muss man außerdem klären, wie viel Downtime akzeptabel ist, ob ein unternehmensweit synchronisiertes Versionsupdate möglich ist oder ob man für eine Weile sowohl das alte als auch das neue Schema unterstützt.
      Wenn die Datenbank nicht Teil des Kernprodukts des Teams ist, kann all das vollständig fehlen.
  • Ich habe diesen Artikel geschrieben, um Anfängern zu helfen: https://tomcam.github.io/postgres/

  • Der Artikel ist wirklich gut, und ich wusste nicht, dass die PostgreSQL-Dokumentation 3200 Seiten umfasst.
    Ich nutze es schon eine Weile und lerne bei Bedarf dazu; die offizielle Dokumentation gefällt mir ziemlich gut, und ich lese auch gern Artikel zu bestimmten Themen, wenn sie relevant werden.
    Ich glaube, es wäre für Leser hilfreich, wenn der Autor unter https://challahscript.com/what_i_wish_someone_told_me_about_... ergänzen würde, dass ein Index auf den Spalten (b, a) gut funktioniert, wenn nur nach b gesucht wird.
    Wenn es um Abfragen nur nach a geht, wird das zwar gewissermaßen angedeutet, aber etwas expliziter wäre nicht schlecht.
    Den Abschnitt zu JSON/JSONB habe ich mir nicht sehr genau angesehen, weil ich das kaum verwende.

  • Wenn ich an den albernen SQL-Code denke, den ich in der Praxis gesehen habe, wäre es ein guter Anfang, das Codd-Paper zu lesen und zu verstehen, was das relationale Modell ist.
    Es hat nur 11 Seiten, und allein das Lesen würde das Leid in dieser Welt verringern.

  • Fast alles in diesem Artikel gilt auch für andere MVCC-Datenbanken wie MySQL.
    Die Details können sich unterscheiden, aber auch MySQL leidet unter langen Transaktionen, hält bei ALTER Metadata Locks und hat ähnliche unterhaltsame Probleme.