Datenbank-Migrationsskripte schreiben, die die Produktion nicht sprengen
Du hast ein neues Feature fertig. Der Code ist reviewed, getestet und gemergt. Aber eines steht noch zwischen dir und dem Deployment: eine Datenbankänderung. Vielleicht musst du eine Spalte hinzufügen, eine Tabelle umbenennen oder einen neuen Index einführen. Die Frage ist nicht, ob die Änderung auf deinem Laptop funktioniert. Die Frage ist, ob sie in der Produktion funktioniert, ohne etwas zu zerstören.
Hier kommen Migrationsskripte ins Spiel. Sie sind nicht nur SQL-Dateien. Sie sind eine disziplinierte Methode, um dein Datenbankschema ohne Rätselraten, ohne manuelle Schritte und ohne das flaue Gefühl im Magen weiterzuentwickeln, wenn ein Deployment schiefgeht.
Das grundlegende Muster: Eine Datei, eine Änderung
Die Kernidee ist einfach. Jede Datenbankänderung lebt in einer eigenen Datei. Die Datei hat eine eindeutige Kennung, normalerweise einen Zeitstempel oder eine Sequenznummer, und enthält das SQL, um die Änderung anzuwenden. Du erstellst auch eine passende Rollback-Datei, die die Änderung rückgängig machen kann.
Angenommen, du musst eine phone-Spalte zur users-Tabelle hinzufügen. Anstatt dich in die Produktion einzuloggen und direkt ALTER TABLE auszuführen, erstellst du eine Datei namens 20241101_add_phone_to_users.sql. Darin schreibst du:
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
Dann erstellst du 20241101_add_phone_to_users_rollback.sql:
ALTER TABLE users DROP COLUMN phone;
Beide Dateien kommen zusammen mit deinem Anwendungscode ins Repository. Sie durchlaufen ein Code-Review. Sie werden gemergt wie jede andere Änderung.
Warum separate Dateien? Weil jede Änderung ihr eigenes Risiko birgt. Wenn du Änderungen in einzelne Dateien aufteilst, kannst du eine Änderung anwenden, die Auswirkungen beobachten und dann zur nächsten übergehen. Wenn alles in einem riesigen Skript zusammengefasst ist, kannst du nicht sagen, welcher Teil einen Fehler verursacht hat. Schlimmer noch: Wenn die Migration auf halbem Weg fehlschlägt, hast du keine Ahnung, wo sie gestoppt hat.
Die Reihenfolge ist wichtiger, als du denkst
Der Zeitstempel oder die Sequenznummer ist nicht nur eine Namenskonvention. Sie definiert die Ausführungsreihenfolge. Deine Migrations-Pipeline liest alle Dateien, sortiert sie nach dieser Kennung und führt sie von der ältesten zur neuesten aus. Das garantiert, dass jede Umgebung – Entwicklung, Staging, Produktion – Änderungen in derselben Reihenfolge anwendet.
Schluss mit „Auf meinem Rechner funktioniert es, aber auf dem Server nicht“, weil die Migrationsreihenfolge anders war. Schluss mit stillen Inkonsistenzen, bei denen eine Umgebung einen Schritt übersprungen hat.
Mach deine Skripte idempotent
Idempotent ist ein schickes Wort für eine einfache Idee: Das zweimalige Ausführen desselben Skripts sollte das gleiche Ergebnis liefern und keinen Fehler verursachen.
Vergleiche diese beiden Anweisungen:
-- Nicht idempotent: Fehler, wenn die Spalte bereits existiert
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Idempotent: Kann mehrfach ausgeführt werden
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
Die zweite Version ist sicherer. Pipelines müssen manchmal Migrationen von Grund auf neu ausführen, zum Beispiel beim Aufsetzen einer frischen Staging-Datenbank. Wenn deine Skripte nicht idempotent sind, wird aus dieser einfachen Operation eine Debugging-Session.
Idempotenz hilft auch während der Entwicklung. Entwickler wenden häufig eine Migration an, inspizieren das Ergebnis und möchten von vorne beginnen. Mit idempotenten Skripten können sie die Migration erneut ausführen, ohne die gesamte Datenbank löschen und neu erstellen zu müssen.
Rollback ist kein optionales Extra
Jede Vorwärts-Migration muss ein passendes Rollback haben. Das gilt nicht nur für Produktions-Notfälle. Entwickler nutzen Rollbacks ständig in der lokalen Entwicklung, um Änderungen zu testen und zu iterieren. Ohne Rollback-Skripte müssen sie die gesamte Datenbank löschen und alle Migrationen von Grund auf neu ausführen – das ist langsam und frustrierend.
Aber die ehrliche Wahrheit ist: Rollbacks können nicht immer Daten wiederherstellen. Wenn eine Migration eine Spalte löscht, kann das Rollback diese Spalte zwar neu erstellen, aber die Daten sind weg. Wenn eine Migration eine Tabelle umbenennt, kann das Rollback sie zurückbenennen, aber alle Schreibvorgänge, die in der Zwischenzeit stattgefunden haben, sind verloren.
Das heißt nicht, dass Rollbacks nutzlos sind. Es bedeutet, dass du verstehen musst, was dein Rollback tatsächlich wiederherstellt. Manchmal stellt es nur die Schemastruktur wieder her, nicht die Daten. Das ist trotzdem wertvoll. Es bringt dich in einen bekannten Zustand zurück, in dem du die Änderungen wiederherstellen oder erneut anwenden kannst.
Bei destruktiven Änderungen – Spalten löschen, Tabellen entfernen, Datentypen ändern – brauchst du eine separate Strategie. Darauf gehen wir später ein. Für jetzt gilt die einfache Regel: Jede Migration bekommt ein Rollback, auch wenn es nur die Struktur wiederherstellt.
Wie parallele Arbeit sicher bleibt
Mehrere Entwickler, die an verschiedenen Features arbeiten, brauchen oft Datenbankänderungen. Einer fügt eine Spalte für Feature A hinzu. Ein anderer erstellt eine Tabelle für Feature B. Beide erstellen Migrationsdateien mit unterschiedlichen Zeitstempeln. Wenn beide Branches gemergt werden, sortiert die Pipeline die Dateien nach Zeitstempel und wendet sie in der Reihenfolge an.
Konflikte treten nur auf, wenn zwei Änderungen dasselbe Schemaobjekt betreffen. Das ist ein echter Konflikt, der eine menschliche Lösung erfordert, genau wie ein Code-Konflikt. Das Migrationsdatei-Muster beseitigt das nicht, aber es macht den Konflikt sichtbar und explizit.
Die Tracking-Tabelle
Woher weiß deine Pipeline, welche Migrationen bereits ausgeführt wurden? Sie verwendet eine spezielle Tabelle innerhalb der Datenbank selbst. Diese Tabelle zeichnet jede angewandte Migration zusammen mit einem Zeitstempel oder einer Prüfsumme auf. Wenn die Pipeline läuft, überprüft sie diese Tabelle, vergleicht sie mit der Liste der Migrationsdateien und wendet nur die fehlenden an.
Dieser Mechanismus ist in die meisten Migrationstools eingebaut, aber das Verständnis hilft dir beim Debuggen, wenn etwas schiefgeht. Wenn eine Migration nur teilweise angewandt wurde oder jemand manuell ein Skript außerhalb der Pipeline ausgeführt hat, zeigt dir die Tracking-Tabelle das an.
Praktische Checkliste für das Schreiben von Migrationsskripten
Bevor du diese Migrationsdatei mergst, geh diese kurze Liste durch:
- Hat das Skript eine eindeutige Kennung (Zeitstempel oder Sequenz)?
- Gibt es ein passendes Rollback-Skript?
- Ist das Skript idempotent? Kann es zweimal ohne Fehler ausgeführt werden?
- Stellt das Rollback tatsächlich den vorherigen Zustand wieder her?
- Hast du sowohl die Vorwärts- als auch die Rückwärts-Migration auf einer Kopie der Produktionsdaten getestet?
- Sperrt die Migration Tabellen? Falls ja, kann sie bei geringem Traffic ausgeführt werden?
Das Fazit
Datenbank-Migrationen sind nicht nur SQL. Sie sind ein Vertrag zwischen deinem Team und deinen Produktionsdaten. Jede Migrationsdatei repräsentiert eine Entscheidung: Was ändert sich, in welcher Reihenfolge und wie macht man es rückgängig, falls etwas schiefgeht. Behandle jede Datei mit der gleichen Sorgfalt wie deinen Anwendungscode. Review sie. Teste sie. Stelle sicher, dass sie ein Rollback hat. Deine Produktionsdatenbank wird es dir danken.