- Ein Artikel, der praktische Muster zusammenfasst, mit denen sich Postgres produktiver und sicherer nutzen lässt
- Jedes einzelne Muster ist klein, aber in der Summe machen sie einen großen Unterschied
UUIDs als Primärschlüssel verwenden
- UUIDs sind zufällig und haben deshalb Nachteile bei Sortierung und Index-Performance
- Sie benötigen mehr Speicherplatz als numerische IDs
- Sie haben jedoch folgende Vorteile
- UUIDs lassen sich auch ohne Verbindung zur DB erzeugen
- Sie können sicher nach außen offengelegt werden
- Mit
gen_random_uuid() lassen sich UUIDs automatisch als Primärschlüssel erzeugen
Felder created_at und updated_at immer hinzufügen
- Beim Debugging ist es sehr nützlich zu wissen, wann ein Datensatz erstellt und geändert wurde
updated_at kann so konfiguriert werden, dass es per Trigger automatisch aktualisiert wird
- Die Funktion muss nur einmal erstellt werden, der Trigger jedoch für jede Tabelle einzeln
Für Fremdschlüssel on update restrict / on delete restrict setzen
- Beim Definieren von Fremdschlüssel-Constraints sollte unbedingt
on update restrict on delete restrict verwendet werden
- So wird verhindert, dass beim Löschen von Daten versehentlich kaskadierende Löschungen ausgelöst werden
- Speicherplatz ist billig, aber Datenwiederherstellung ist sehr schwierig, daher ist ein konservativer Umgang sinnvoll
Die Verwendung von Schemas wird empfohlen
- Das Standard-Schema ist
public, aber wenn eine Anwendung wächst, ist es besser, sie in separate Schemas aufzuteilen
- Schemas funktionieren wie Namespaces, und Joins zwischen unterschiedlichen Schemas sind ebenfalls möglich
- Je mehr Tabellen es gibt, desto vorteilhafter ist der Einsatz von Schemas für Lesbarkeit und Wartbarkeit
Das Enum-Tabellenmuster verwenden
- Statt des Enum-Typs von PostgreSQL oder einer Check Constraint ist die Verwendung von Enum-Tabellen flexibler
- Werden Enum-Werte in einer separaten Tabelle verwaltet, lassen sich Metadaten hinzufügen oder Enum-Werte leicht erweitern
- Die Werte der Enum-Tabelle werden per Fremdschlüssel referenziert, um die Constraints beizubehalten
Tabellennamen im Singular vergeben
- Tabellennamen sollten vorzugsweise im Singular statt im Plural vergeben werden
- Beim Schreiben von Queries ist der Singular klarer, während der Plural zu grammatikalischer oder semantischer Verwirrung führen kann
Join-Tabellen mechanisch benennen
- Join-Tabellen für Many-to-many-Beziehungen sollten sicher und eindeutig benannt werden, indem die beiden Tabellennamen zusammengesetzt werden
- Beispiel:
person_pet
- Ergänze einen eindeutigen Index auf die Kombination, um Duplikate zu verhindern
Soft Delete statt echter Löschung verwenden
- Statt Daten tatsächlich zu löschen, ist es besser, ein Timestamp-Feld wie
revoked_at zu verwenden, das den Löschzeitpunkt angibt
- So lässt sich nicht nur erkennen, ob etwas gelöscht wurde, sondern auch wann
- Ein Timestamp liefert mehr Informationen als ein Boolean-Wert
Status als Log-Tabelle modellieren
- Statt den Status in einer einzelnen Spalte auszudrücken, sollte der Verlauf von Statusänderungen in einer separaten Tabelle gespeichert werden
- Der Zeitpunkt eines Statusereignisses wird explizit in der Spalte
valid_at festgehalten
- Für schnelle Abfragen des aktuellen Status werden ein
latest-Flag sowie ein eindeutiger Index und ein Trigger eingerichtet
- Das ist vorteilhaft bei asynchroner Event-Verarbeitung oder in Situationen, in denen die Reihenfolge durcheinandergeraten kann
Für besondere Zeilen system_id hinzufügen
- Nicht nur bei Enum-Tabellen, auch sonst werden manchmal bestimmte „System-Zeilen“ benötigt
- Füge ein nullable Textfeld
system_id hinzu und setze einen eindeutigen Index darauf
- Über
system_id lassen sich bestimmte Zeilen eindeutig abfragen
Views nur sparsam einsetzen
- Views sind nützlich, um komplexe Queries zu abstrahieren, aber sie sind schwer zu warten
- Wenn Spalten entfernt werden, müssen Views neu erstellt werden
- Werden Views auf Views aufgebaut, entstehen Probleme bei Performance und Lesbarkeit
- Deshalb sollten sie nur so weit wie nötig und mit Bedacht eingesetzt werden
JSON-Queries aktiv nutzen
- Postgres ist nicht nur beim Speichern von JSON stark, sondern auch bei Queries, die JSON zurückgeben
- Verschachtelte Beziehungen lassen sich mit einer einzigen Query als JSON zurückgeben
- So können alle benötigten Daten ohne N+1-Problem auf einmal geladen werden
- Nachteil: Typinformationen gehen verloren, und alle Daten müssen auf einmal in den Speicher geladen werden
- Die Vorteile bei Performance oder Struktur überwiegen jedoch deutlich
4 Kommentare
> Join-Tabellen mechanisch benennen
Ich finde es an sich gut, dass es überhaupt so eine Regel für die Benennung gibt~
Wenn man UUID7 in Betracht zieht, wäre dann keine Sortierung nach Zeit möglich?
Der Beitrag Zur Verwendung von PostgreSQL mit UUIDs als Primärschlüssel ist ebenfalls lesenswert.
Die Methode, bei Soft Deletes einen Timestamp zu setzen, ist gut. Wenn man als Primärschlüssel eine UUID verwendet, ist keine Sortierung nach Zeit möglich; daher scheint es ebenfalls sinnvoll zu sein, eine Snowflake-ID oder ULID zu verwenden. In diesem Fall muss allerdings jeder Server eine Sequence-Number vorhalten.