Wenn ein Index deine Anwendung einfriert
Dein Team jagt seit Wochen einer langsamen Query hinterher. Die users-Tabelle ist auf Millionen von Zeilen angewachsen, und die Suche nach E-Mail-Adressen dauert jetzt Sekunden statt Millisekunden. Jemand schlägt vor, einen Index hinzuzufügen. Der Fix scheint einfach: ein CREATE INDEX-Befehl, Problem gelöst.
Du führst die Migration während der Arbeitszeit aus, weil die Query die User bereits beeinträchtigt. Der Index beginnt mit dem Aufbau. Dann kommen die Alarme: Application-Timeouts, fehlgeschlagene Transaktionen, Support-Tickets stapeln sich. Der Index, den du zur Performance-Verbesserung hinzugefügt hast, hat gerade einen Produktionsvorfall ausgelöst.
Dieses Szenario tritt häufiger auf, als die meisten Teams erwarten. Indizes und Constraints sehen wie einfache Schema-Änderungen aus, aber sie haben versteckte Kosten, die sich erst unter Last zeigen.
Was passiert, wenn du einen Index erstellst
Wenn du CREATE INDEX idx_users_email ON users(email) ausführst, muss die Datenbank jede Zeile in der Tabelle lesen, eine balancierte Baumstruktur aufbauen und auf die Festplatte schreiben. Während dieses Prozesses sperrt die Datenbank die Tabelle normalerweise, um Datenänderungen zu verhindern, die die Indexstruktur beschädigen würden.
Die Art der Sperre hängt von deiner Datenbank ab. In PostgreSQL erzeugt ein reguläres CREATE INDEX eine SHARE LOCK. Lesevorgänge sind weiterhin möglich, aber alle Schreiboperationen – Einfügungen, Aktualisierungen, Löschungen – müssen warten. In MySQL mit InnoDB variiert das Sperrverhalten je nach Operationstyp und Storage-Engine-Version.
Bei einer Tabelle, die Tausende von Benutzern bedient, können selbst wenige Sekunden Schreibsperre einen Rückstau erzeugen. Anwendungsverbindungen reihen sich ein, Timeouts kaskadieren, und aus einer vermeintlichen Performance-Verbesserung wird ein Ausfallereignis.
Die parallele Option, die die meisten Teams vergessen
Die meisten großen Datenbanken bieten eine Möglichkeit, Indizes zu erstellen, ohne Schreibvorgänge zu blockieren. PostgreSQL hat CREATE INDEX CONCURRENTLY. MySQL unterstützt ALGORITHM=INPLACE und LOCK=NONE auf kompatiblen Versionen.
-- PostgreSQL: baut Index ohne Schreibsperre auf
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- MySQL: baut Index ohne Schreibsperre auf (InnoDB)
ALTER TABLE users ADD INDEX idx_users_email (email), ALGORITHM=INPLACE, LOCK=NONE;
Der parallele Ansatz dauert länger. PostgreSQLs CREATE INDEX CONCURRENTLY muss auf den Abschluss laufender Transaktionen warten und dann zusätzliche Durchläufe ausführen, um während des Aufbaus vorgenommene Änderungen zu erfassen. Aber der Trade-off ist klar: Deine Anwendung läuft weiter, während der Index erstellt wird.
Der Fehler ist, die parallele Indexerstellung als optional oder fortgeschritten zu betrachten. Sie sollte der Standard für jede Produktionsmigration auf einer Live-Tabelle sein. Schreibe sie explizit in deine Migrationsskripte. Verlasse dich nicht auf das Standardverhalten.
Auch Constraints sperren
Fremdschlüssel- und Unique-Constraints bringen ihre eigenen Überraschungen beim Sperren mit sich. Wenn du einen Fremdschlüssel hinzufügst, validiert die Datenbank, dass alle vorhandenen Zeilen die Beziehung erfüllen. Diese Validierung liest die gesamte Tabelle und sperrt sie während des Vorgangs.
PostgreSQL bietet einen pragmatischen Ausweg: NOT VALID. Dies fügt den Constraint nur für neue Daten hinzu und überspringt die Validierung vorhandener Zeilen. Anschließend kannst du VALIDATE CONSTRAINT separat außerhalb der Hauptgeschäftszeiten ausführen.
-- Fremdschlüssel hinzufügen, ohne vorhandene Daten zu validieren
ALTER TABLE orders ADD CONSTRAINT fk_user_id
FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID;
-- Später validieren, wenn der Traffic niedrig ist
ALTER TABLE orders VALIDATE CONSTRAINT fk_user_id;
Unique-Constraints haben eine ähnliche Geschichte. Das Hinzufügen von UNIQUE für eine Spalte erfordert die Überprüfung auf Duplikate in der gesamten Tabelle. Bei einer großen Tabelle kann dieser Scan Schreibvorgänge für eine unangenehme Dauer sperren.
Plane Index-Migrationen separat
Viele Teams packen alle Schema-Änderungen in eine Migrationsdatei: eine Spalte hinzufügen, einen Index erstellen, einen Fremdschlüssel hinzufügen. Das funktioniert gut bei kleinen Tabellen. Bei Produktionstabellen mit Millionen von Zeilen erzeugt es einen Single Point of Failure.
Index- und Constraint-Änderungen verdienen ihre eigenen Migrationsdateien, die getrennt von Tabellenstrukturänderungen geplant werden. Einige Teams gehen noch weiter und führen Index-Migrationen manuell außerhalb der automatisierten Pipeline durch, um die Kontrolle über das Timing zu behalten.
Ein praktischer Ansatz:
- Führe Tabellenstrukturänderungen (Spalte hinzufügen, Typ ändern) in einer Migration durch
- Führe Index- und Constraint-Änderungen in separaten Migrationen durch
- Plane Index-Migrationen in verkehrsarmen Zeitfenstern
- Verwende parallele Optionen explizit in jedem Migrationsskript
- Validiere Constraints getrennt vom Hinzufügen
Die eigentliche Frage
Sobald Indizes und Constraints vorhanden sind, musst du überprüfen, ob sie die laufende Anwendung nicht beeinträchtigen. Ein Index, der Ausführungspläne ändert, kann unerwartete Performance-Verschiebungen verursachen. Ein Fremdschlüssel, der eine Löschkaskade blockiert, kann Hintergrundjobs zum Stillstand bringen.
Dies führt zu einer breiteren Frage: Wie hältst du Migrationen sicher, wenn alte und neue Versionen deiner Anwendung auf dieselbe Datenbank zugreifen? Das ist das Thema der nächsten Diskussion, aber das Prinzip beginnt hier: Behandle jede Schema-Änderung als potenzielles Produktionsereignis, nicht nur als ein Skript, das ausgeführt wird.
Checkliste für Index- und Constraint-Migrationen
- Verwende parallele Indexerstellung (
CREATE INDEX CONCURRENTLYoder Äquivalent) bei jeder Tabelle mit aktiven Schreibvorgängen - Füge Fremdschlüssel nach Möglichkeit mit
NOT VALIDhinzu, validiere separat - Führe Index-Migrationen in separaten Dateien von Tabellenstrukturänderungen durch
- Plane große Indexerstellungen in verkehrsarmen Zeitfenstern
- Überwache die Sperrdauer und die Query-Performance während und nach der Migration
- Teste die Migration auf einer Kopie der Produktionsdaten, bevor du sie live ausführst
Das Fazit
Ein Index ist nicht nur ein Performance-Werkzeug. Es ist eine Schema-Änderung, die deine Tabelle sperren, deine Schreibvorgänge in die Warteschlange stellen und deine Benutzer in Timeouts treiben kann. Behandle sie mit der gleichen Vorsicht wie jedes andere Produktions- Deployment. Verwende parallele Optionen, trenne deine Migrationen und plane für Sicherheit. Die gewonnene Query-Geschwindigkeit ist wertlos, wenn die Migration selbst die Anwendung zerstört.