Datenbankadministration für Sysadmins

Schemaänderungen im laufenden Betrieb: Sperrstufe, Riegel und Rückweg

Der ALTER TABLE lief in 200 Millisekunden. Die Anwendung war 40 Minuten nicht benutzbar, und im Protokoll steht kein einziger Fehler. Beides passt zusammen: Die Sperre, die eine Strukturänderung nimmt, blockiert auch gewöhnliche Leseabfragen — und ohne Zeitgrenze wartet sie unbegrenzt auf eine fremde Transaktion. Dieses Modul behandelt Sperrstufen je Unterbefehl, den Riegel, der aus Stillstand einen Fehlschlag macht, und das Muster, mit dem eine Spalte ohne Ausfall ersetzt wird.

Lehrtext · 11 Abschnitte · zuletzt geprüft: 2026-09-12

Eine Schemaänderung ist eine Auslieferung, kein Befehl

ALTER TABLE sieht aus wie eine Zeile und verhält sich wie ein Wartungsfenster. Der Unterschied zu einer Dateiänderung auf einem Anwendungsserver ist, dass die Datenbank einen Zustand hat, den alle teilen: Während die Struktur wechselt, kann niemand sie in zwei Fassungen gleichzeitig sehen.

Daraus folgt die Grundspannung dieses Moduls. Eine Auslieferung läuft nie in einem Augenblick ab — es gibt immer eine Phase, in der alte und neue Anwendungsfassung gleichzeitig laufen. Das Schema muss in dieser Phase beiden genügen. Wer es in einem Schritt umstellt, erzwingt einen Stillstand; wer es in mehreren Schritten tut, muss jeden Schritt rückwärtskompatibel halten.

Die zwei Fragen, die jede Schemaänderung beantworten mussWelche Sperre nimmt sie, und wie lange? — das entscheidet über die Verfügbarkeit während der Änderung. Und: Funktioniert die alte Anwendungsfassung danach noch? — das entscheidet darüber, ob ein Rückweg existiert. Die erste Frage ist technisch, die zweite ist Entwurf.

Welche Sperre der Befehl wirklich nimmt

Die Dokumentation zu ALTER TABLE beginnt mit einer Voreinstellung, die man kennen muss, bevor man irgendeine Unterform nachschlägt.

Wörtlich aus der DokumentationDie erforderliche Sperrstufe kann sich für jede Unterform unterscheiden. Eine ACCESS EXCLUSIVE-Sperre wird erlangt, sofern nicht ausdrücklich anders vermerkt. Werden mehrere Unterbefehle angegeben, ist die erlangte Sperre die strengste, die einer der Unterbefehle verlangt.

Der zweite Satz ist der, der in der Praxis überrascht. Wer drei harmlose Änderungen in einem Befehl zusammenfasst, weil das übersichtlicher aussieht, bekommt die strengste Sperre von allen dreien — für die Dauer aller drei. Getrennte Befehle nehmen jeweils nur, was sie brauchen, und geben früher wieder frei.

Voreinstellung
ACCESS EXCLUSIVE — die strengste Stufe, sofern die Dokumentation zur Unterform nichts anderes sagt.
SET STATISTICS, Attributoptionen
Nur SHARE UPDATE EXCLUSIVE. Blockiert keine Leser und keine Schreiber.
ADD FOREIGN KEY
Nur SHARE ROW EXCLUSIVE — und zwar zusätzlich auch auf der referenzierten Tabelle. Die zweite Sperre wird bei der Planung regelmäßig übersehen.
Die meisten anderen ADD CONSTRAINT-Formen
ACCESS EXCLUSIVE.
Warum die zweite Sperre zähltEin Fremdschlüssel auf eine zentrale Stammdatentabelle sperrt diese Stammdatentabelle mit. Eine Änderung, die nach einer kleinen Detailtabelle aussieht, kann so den meistgenutzten Datenbestand des Hauses berühren — und das steht nicht in der Migrationsdatei, sondern nur in der Referenz.

Die Sperre, die wirklich alles trifft

Unter den Sperrmodi ist einer besonders, und ALTER TABLE nimmt ihn voreingestellt. ACCESS EXCLUSIVE steht laut Konfliktmatrix der Dokumentation mit allen Modi in Konflikt — auch mit ACCESS SHARE, dem Modus, den ein ganz gewöhnliches SELECT erwirbt.

Das heißt: Solange diese Sperre gehalten wird, geht auf dieser Tabelle nicht nur kein Schreibvorgang mehr, sondern keine einzige Leseabfrage. Eine Schemaänderung ist damit nicht ein Eingriff in die Schreibseite, sondern ein vollständiger Stillstand der Tabelle.

Und das Warten selbst ist gefährlichErlangt der Befehl seine Sperre nicht sofort, wartet er — laut Dokumentation unbegrenzt, solange kein Deadlock erkannt wird. Sperranforderungen werden dabei in Warteschlangen geführt; die Dokumentation zu pg_locks erwähnt sie ausdrücklich, gibt ihre Reihenfolge aber nicht preis. Die praktische Folge — dokumentiert ist sie in dieser Form nicht, beobachtbar aber zuverlässig — ist, dass sich hinter einer wartenden ACCESS EXCLUSIVE-Anforderung weitere Anfragen aufstauen. Aus einer Migration, die „nur ein paar Sekunden dauert“, wird dann ein Stillstand von der Länge der längsten gerade laufenden Transaktion.

Warum die Migration 40 Minuten Ausfall erzeugte, obwohl sie 200 Millisekunden lief

Szenario

Ein ALTER TABLE ... ADD COLUMN wurde um 10:00 Uhr ausgeführt. Der Befehl selbst brauchte 200 Millisekunden. Die Anwendung war von 10:00 bis 10:40 Uhr nicht benutzbar.

Anforderungen

  • Eine Auswertungsabfrage lief seit 09:22 Uhr auf derselben Tabelle
  • Kein lock_timeout gesetzt
  • Keine Fehlermeldung im Protokoll — alles wartete nur

Schritte

  1. Die Auswertung hielt ACCESS SHARE. Das steht mit ACCESS EXCLUSIVE in Konflikt, also musste die Migration warten.
  2. Sie wartete, bis die Auswertung um 10:40 Uhr fertig war — unbegrenzt, weil nichts sie abbrach.
  3. Während dieser 40 Minuten stauten sich die Anfragen der Anwendung hinter der wartenden Anforderung.
  4. Um 10:40 Uhr lief die Migration in 200 Millisekunden durch, und alles wurde wieder normal — weshalb die Nachbetrachtung den Befehl für unschuldig hielt.

Merksatz: Die Dauer einer Schemaänderung ist nicht ihre Ausführungszeit, sondern ihre Wartezeit plus ihre Ausführungszeit. Die erste hängt an fremden Transaktionen und ist ohne Riegel unbegrenzt.

`lock_timeout`: der Riegel, der aus Stillstand einen Fehlschlag macht

Gegen genau dieses Muster gibt es eine Einstellung, und sie ist voreingestellt aus.

Aus der Dokumentationlock_timeout bricht jede Anweisung ab, die länger als die angegebene Zeit auf eine Sperre auf einer Tabelle, einem Index, einer Zeile oder einem anderen Datenbankobjekt wartet. Die Zeitgrenze gilt für jeden Sperrerwerb einzeln. Der Wert null — die Voreinstellung — schaltet sie ab. Anders als statement_timeout kann diese Zeitüberschreitung nur beim Warten auf Sperren eintreten.

Damit wird aus einem unbegrenzten Stillstand ein schneller, sichtbarer Fehlschlag. Die Migration bricht nach wenigen Sekunden ab, die Anwendung bleibt erreichbar, und der Auslieferungsprozess meldet einen Fehler statt eines Hängers. Der zweite Versuch kann dann in einem Moment laufen, in dem keine lange Abfrage im Weg steht.

Zwei Feinheiten, die die Dokumentation ausdrücklich nenntErstens: Ist statement_timeout ungleich null, ist es sinnlos, lock_timeout auf denselben oder einen größeren Wert zu setzen — die Anweisungs-Zeitüberschreitung würde immer zuerst greifen. Zweitens: Beide in postgresql.conf zu setzen wird nicht empfohlen, weil es alle Sitzungen beträfe. Der richtige Ort ist die Migrationssitzung selbst.

Praktisch heißt das: Ein Auslieferungsskript setzt lock_timeout auf wenige Sekunden, bevor es die erste Schemaänderung absetzt — und nimmt in Kauf, dass es dadurch öfter fehlschlägt. Genau das ist der Gewinn: Ein Fehlschlag kostet einen zweiten Anlauf, ein Stillstand kostet den Dienst.

Zweistufig statt einstufig

Die teuerste Zutat vieler Schemaänderungen ist nicht die Strukturänderung, sondern die Prüfung des Bestands. Eine neue Bedingung verlangt normalerweise, dass jede vorhandene Zeile sie erfüllt — und das heißt, die ganze Tabelle zu lesen, während die Sperre gehalten wird.

Die Dokumentation bietet dafür einen zweistufigen Weg an. Mit der Option NOT VALID wird dieser potenziell langwierige Durchlauf übersprungen. Die Bedingung gilt trotzdem für alle nachfolgenden Einfüge- und Änderungsvorgänge — die schlagen ab sofort fehl, wenn sie sie verletzen. Die Datenbank nimmt nur nicht an, dass die Bedingung für alle bestehenden Zeilen gilt, bis sie mit VALIDATE CONSTRAINT bestätigt wurde.

Schritt 1: ADD CONSTRAINT ... NOT VALID
Kurz, weil kein Tabellendurchlauf. Ab sofort sind neue und geänderte Zeilen geprüft.
Zwischenzeit
Bestand bereinigen, soweit nötig. Die Anwendung läuft weiter, neue Verstöße können nicht mehr entstehen.
Schritt 2: VALIDATE CONSTRAINT
Der Durchlauf über den Bestand — mit einer schwächeren Sperre als die einstufige Variante.
Dasselbe Muster beim IndexDie Dokumentation nennt einen verwandten Weg ausdrücklich für Situationen, in denen eine neue Bedingung hinzukommen soll, ohne Tabellenänderungen lange zu blockieren: Zuerst den eindeutigen Index nebenläufig anlegen, dann diesen vorhandenen Index in eine Bedingung umwandeln. Der teure Teil — der Indexaufbau — läuft dann ohne Schreibblockade, und die Umwandlung selbst ist kurz.

Die Kollision, die niemand einplant

Es gibt einen Sperrmodus, der weder Leser noch Schreiber stört und trotzdem regelmäßig Auslieferungen zum Scheitern bringt. Er heißt SHARE UPDATE EXCLUSIVE, und die Dokumentation beschreibt seinen Zweck in einem Satz: Dieser Modus schützt eine Tabelle gegen gleichzeitige Schemaänderungen und Aufräumläufe.

Genommen wird er unter anderem vom Aufräumen und Analysieren — also von dem, was der Autovacuum-Daemon selbsttätig und ohne Ankündigung tut — und vom nebenläufigen Indexaufbau. Entscheidend ist, dass er laut Konfliktmatrix mit sich selbst in Konflikt steht: Zwei Vorgänge dieses Modus können nicht gleichzeitig auf derselben Tabelle laufen.

Autovacuum läuft, Migration startet
Die Migration wartet, bis der Aufräumlauf fertig ist. Auf einer sehr großen Tabelle kann das Stunden bedeuten — und die Migration hält währenddessen die Anfragen hinter sich auf.
Migration läuft, Autovacuum will starten
Der Aufräumlauf wird übersprungen. Passiert das wiederholt, bläht die Tabelle auf, ohne dass jemand einen Fehler sieht.
Wie man das überhaupt bemerktGenau hier greift die Einstellung aus dem Wartungsmodul: Sobald log_autovacuum_min_duration nicht auf „aus“ steht, wird laut Dokumentation auch dann ein Eintrag geschrieben, wenn ein Aufräumvorgang wegen einer konfliktierenden Sperre übersprungen wurde. Ohne diese Einstellung ist die Kollision unsichtbar — man sieht nur eine Tabelle, die seit Monaten wächst, und eine Migration, die manchmal ungewöhnlich lange braucht.

Praktisch heißt das: Eine Auslieferung auf eine große, stark geänderte Tabelle gehört nicht in ein Zeitfenster, in dem erfahrungsgemäß aufgeräumt wird — und ein Blick in die Sitzungsübersicht vor dem Start beantwortet die Frage, ob gerade ein Aufräumlauf auf dem Zielobjekt arbeitet. Das ist derselbe Blick, den auch die Sperr-Zeitgrenze ersetzt; nur erfährt man mit ihm den Grund und nicht nur das Scheitern.

Erweitern, umstellen, zusammenziehen

Das Muster, das eine Auslieferung ohne Stillstand überhaupt erst möglich macht, hat drei Schritte und dauert mehrere Releases. Es ist kein Trick, sondern die Konsequenz daraus, dass alte und neue Anwendungsfassung eine Zeit lang gleichzeitig laufen.

Erweitern
Das Neue hinzufügen, ohne das Alte anzutasten. Neue Spalte nullbar, neue Tabelle leer, neuer Index zusätzlich. Die alte Anwendungsfassung merkt nichts.
Umstellen
Die Anwendung ausliefern, die auf das Neue schreibt — und, solange nötig, auch noch auf das Alte. Bestandsdaten im Hintergrund nachziehen, in Portionen statt in einem Zug.
Zusammenziehen
Erst wenn keine laufende Anwendungsfassung das Alte mehr braucht: das Alte entfernen. Das ist der einzige Schritt, der nicht mehr rückwärtskompatibel ist.

Der Preis steht dem Gewinn deutlich gegenüber. Zwischen Schritt eins und drei liegen Wochen, in denen das Schema beides trägt: doppelte Spalten, doppelte Schreibpfade, Bestandsdaten in zwei Zuständen. Wer den dritten Schritt vergisst — und das passiert oft, weil er nichts Sichtbares bringt —, sammelt dauerhaft Altlasten, die niemand mehr zuzuordnen weiß.

Kurzcheck

Welcher der drei Schritte ist der einzige, der einen Rückweg unmöglich macht?

  • Zusammenziehen — das Entfernen des Alten
  • Erweitern — die neue Spalte ließe sich nicht mehr entfernen
  • Umstellen — die doppelten Schreibpfade lassen sich nicht zurücknehmen

Treffer. Richtig. Erweitern und Umstellen lassen sich zurücknehmen, weil das Alte noch da ist. Sobald es entfernt ist, kann eine ältere Anwendungsfassung nicht mehr laufen.

Die Migration, die auf einen Menschen wartet

Ein Auslieferungsskript, das seine Schemaänderungen in eine Transaktion klammert, tut grundsätzlich das Richtige — die Änderungen werden dann gemeinsam wirksam oder gar nicht. Es erzeugt aber eine neue Gefahr, wenn es in der Mitte stehen bleibt.

Solange die Transaktion offen ist, hält sie alle bisher erlangten Sperren. Bleibt das Skript an einer Rückfrage hängen, an einem abgebrochenen Terminal oder an einer Netzunterbrechung, hält es eine ACCESS EXCLUSIVE-Sperre auf einer Produktivtabelle — auf unbestimmte Zeit.

Die Einstellung dagegenidle_in_transaction_session_timeout beendet laut Dokumentation jede Sitzung, die innerhalb einer offenen Transaktion länger als angegeben untätig auf eine Client-Anfrage wartet. Voreingestellt ist sie null, also aus. Die Dokumentation nennt ausdrücklich beide Gründe für ihren Einsatz: Erstens halten untätige Sitzungen dann keine Sperren mehr unangemessen lange. Zweitens verhindert eine offene Transaktion selbst dann, wenn sie keine nennenswerten Sperren hält, das Aufräumen kürzlich verstorbener Zeilenversionen, die möglicherweise nur für sie sichtbar sind — längeres Untätigsein trägt also zur Aufblähung der Tabellen bei.

Damit schließt sich der Kreis zu zwei anderen Modulen: Dieselbe offene Transaktion, die im Vorfall als Blockierer erscheint, ist im Wartungsmodul die Ursache einer Tabelle, die trotz laufendem Aufräumen weiter aufbläht. Es ist ein Befund mit drei Gesichtern.

Der Rückweg gehört zur Änderung

Bei Anwendungscode ist der Rückweg die vorige Fassung. Bei Schemaänderungen ist er selten so einfach, und die Unterscheidung, die dafür zählt, ist dieselbe wie im Grundlagenmodul: Was lässt sich ohne Datenverlust zurücknehmen?

Umkehrbar ohne Verlust
Eine nullbare Spalte hinzufügen, einen Index anlegen, eine Bedingung als NOT VALID ergänzen, ein Standardwert setzen.
Umkehrbar mit Aufwand
Eine Spalte umbenennen — zurückbenennen geht, aber alles, was den neuen Namen schon benutzt, bricht.
Nicht umkehrbar
Eine Spalte oder Tabelle entfernen, einen Datentyp verengen, Zeilen im Rahmen der Migration löschen. Der Rückweg führt hier über eine Sicherung, nicht über einen Befehl.
Warum ein Rücknahme-Skript oft eine Illusion istEin Skript, das eine Spalte wieder hinzufügt, stellt die Struktur wieder her, nicht die Daten. Nach dem Entfernen ist der Inhalt weg; die wiederhergestellte Spalte ist leer. Wer einen Rückweg braucht, muss ihn deshalb entweder durch Aufschieben des Entfernens offenhalten — also durch das Erweitern-Umstellen-Zusammenziehen-Muster — oder er braucht eine Wiederherstellung aus der Sicherung. Ein drittes gibt es nicht.

Daraus folgt eine praktische Regel für das Auslieferungsfenster: Der zusammenziehende Schritt gehört nicht in dieselbe Auslieferung wie der erweiternde. Zwischen beiden muss mindestens ein Zeitraum liegen, in dem sich zeigt, dass die neue Fassung trägt.

Was das Skript mitbringen muss

Aus allem Vorherigen ergibt sich eine kurze Liste dessen, was in einem Migrationsskript stehen sollte, bevor die erste Strukturänderung kommt.

  • Eine Sperr-Zeitgrenze, gesetzt in der Sitzung und nicht in der Serverkonfiguration — damit ein Wartefall zum Fehlschlag wird statt zum Stillstand.
  • Eine klare Transaktionsklammer. Mehrere zusammengehörige Änderungen gehören in eine Transaktion; ein Werkzeugaufruf, der bei einem Fehler zurückrollt und einen von null verschiedenen Rückgabewert liefert, macht das Scheitern für die Automatisierung sichtbar.
  • Getrennte Befehle statt zusammengefasster, wo die Unterbefehle verschiedene Sperrstufen brauchen — sonst gilt die strengste für alle.
  • Keine Datenmigration im selben Zug. Das Umkopieren großer Bestände gehört in portionierte Läufe außerhalb der Strukturänderung, sonst hält eine Transaktion stundenlang Sperren.
  • Ein benannter Rückweg — und wenn er „aus der Sicherung“ lautet, gehört das vor der Auslieferung geschrieben, nicht danach herausgefunden.
Die Nebenwirkung der WerkzeugklammerEin Aufruf, der die ganze Datei in eine Transaktion fasst, macht die Migration atomar — und verlängert damit die Zeit, in der ihre Sperren gehalten werden, auf die Dauer der gesamten Datei. Bei einer Datei aus drei schnellen Befehlen ist das richtig. Bei einer Datei, die auch einen Indexaufbau enthält, ist es der Grund für den nächsten Ausfall. Atomarität und kurze Sperrdauer stehen hier gegeneinander, und die Abwägung gehört bewusst getroffen.

Die Reihenfolge, die das zusammenhält

Sperrstufe der geplanten Änderung nachschlagen — je UnterbefehlPrüfen, ob eine lange Transaktion auf der Tabelle läuftSperr-Zeitgrenze setzen, bevor die erste Änderung läuftTeure Prüfungen und Aufbauten aus der Sperre herauslösenRückweg benennen, bevor der zusammenziehende Schritt kommt
Sperrstufe vor Zeitplan

Schritt zwei ist der billigste und wird am häufigsten übersprungen. Ein Blick in die Sitzungsübersicht vor der Auslieferung beantwortet die Frage, ob die Migration überhaupt eine Chance hat, ihre Sperre sofort zu bekommen — oder ob sie sich hinter eine Auswertung stellt, die noch eine halbe Stunde läuft.

AbgrenzungWie ein Index aufgebaut wird, ohne die Schreibseite anzuhalten, und wann er sich überhaupt lohnt, steht im Modul über Indizes und Ausführungspläne. Welche Sperrmodi es gibt und warum sie gehalten werden, steht im Modul über Transaktionen und Isolationsstufen. Change-Verfahren, Freigaben und Auslieferungssteuerung als Prozess stehen im Track itsm-operations — hier ging es allein um das, was die Datenbank dabei tut.

Jetzt anwenden

Diesen Stoff gibt es als Modul mit bewerteten Entscheidungs-Checks — dieselbe Einführung, danach die Übungen.

Zum Modul →
Quellen & Aktualität7 Primärquellen · zuletzt geprüft:
  1. 01postgresql.org/docs/current/sql-altertable.html
  2. 02postgresql.org/docs/current/explicit-locking.html
  3. 03postgresql.org/docs/current/view-pg-locks.html
  4. 04postgresql.org/docs/current/runtime-config-client.html
  5. 05postgresql.org/docs/current/indexes-intro.html
  6. 06postgresql.org/docs/current/app-psql.html
  7. 07postgresql.org/docs/current/routine-vacuuming.html