PostgreSQL-Fehler: violates foreign key constraint
ERROR: insert or update on table "orders" violates foreign key constraint "orders_customer_id_fkey"
DETAIL: Key (customer_id)=(42) is not present in table "customers".
-- direzione opposta:
ERROR: update or delete on table "customers" violates foreign key constraint "orders_customer_id_fkey" on table "orders"
DETAIL: Key (id)=(42) is still referenced from table "orders".
Ein Fremdschlüssel ist in einer seiner beiden Richtungen fehlgeschlagen: eine Kindzeile zeigt auf ein Elternteil, das nicht da ist, oder eine Elternzeile wird entfernt, während Kinder sie noch referenzieren. Die Formulierung sagt Ihnen welche — "is not present in" versus "is still referenced from" — und das DETAIL nennt den genauen Schlüsselwert.
Was dieser Fehler bedeutet
Ein Fremdschlüssel ist ein Versprechen, das an beiden Enden geprüft wird:
- "insert or update on child": Sie haben eine Kindzeile geschrieben, deren referenzierender Wert keine passende Elternzeile hat. Der Schreibvorgang wurde abgewiesen.
- "update or delete on parent": das Entfernen (oder Umschlüsseln) des Elternteils würde bestehende Kinder verwaisen lassen. Standardmäßig (
NO ACTION) verweigert PostgreSQL; der Constraint kann stattdessenON DELETE CASCADE,SET NULLusw. deklarieren — eine Schema-Entscheidung, die bei der Constraint-Erstellung getroffen wird, nicht zur Query-Zeit.
Häufige Ursachen
- Falsche oder veraltete ID aus der Anwendung — das Elternteil wurde gelöscht, oder die ID kam aus einer anderen Umgebung.
- Reihenfolge über Transaktionen hinweg: das Eltern-INSERT lief in einer anderen Transaktion, die noch nicht committet hat; das Kind-Insert kann es nicht sehen.
- Massenladevorgänge in der falschen Reihenfolge (Kinder vor Eltern), oder Teil-Ladevorgänge.
- Ein Elternteil ohne Strategie löschen für seine Kinder — die zweite Meldung.
So diagnostizieren Sie ihn
Das DETAIL liefert Ihnen den Constraint-Namen, den Schlüsselwert und beide Tabellennamen. Von da aus:
-- Der Constraint, ausführlich:
\d orders
-- Bereits vorhandene Waisen (um das Ausmaß des Problems bei Ladevorgängen zu verstehen):
SELECT o.*
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE c.id IS NULL;
So beheben Sie ihn
- Kind-Richtung: korrigieren Sie den Wert oder die Reihenfolge — erstellen Sie das Elternteil zuerst, in derselben Transaktion oder einer früher committeten.
- Massenladevorgänge: laden Sie Eltern vor Kindern; wenn die Daten wirklich verschachtelt ankommen, machen Sie den Constraint deferrable und prüfen Sie ihn beim Commit:
ALTER TABLE orders ALTER CONSTRAINT orders_customer_id_fkey DEFERRABLE INITIALLY DEFERRED; - Eltern-Richtung: löschen Sie die Kinder zuerst, oder deklarieren Sie die Absicht im Schema (
ON DELETE CASCADEoderSET NULL). Behandeln Sie CASCADE mit Respekt: ein DELETE kann stillschweigend auf sehr viele Zeilen ausstrahlen — machen Sie es zu einer bewussten Design-Entscheidung, keinem schnellen Fix. - Viele Systeme umgehen die Eltern-Richtung ganz mit Soft Deletes (einer
deleted_at-Spalte) für Entitäten, an denen andere Daten hängen.
🔍
Weiterführend: Den richtigen Index wählen — PostgreSQL indiziert die referenzierende Seite eines Fremdschlüssels nicht automatisch; Löschungen am Elternteil scannen ohne Index das Kind.
