Deaktivieren Sie alle Einschränkungen und Tabellenprüfungen, während Sie einen Speicherauszug wiederherstellen

19

Ich habe einen Speicherauszug meiner PostgreSQL-Datenbank erhalten mit:

pg_dump -U user-name -d db-name -f dumpfile

die ich dann in einer anderen datenbank wiederherstelle mit:

psql X -U postgres  -d db-name-b -f dumpfile

Mein Problem ist, dass die Datenbank referenzielle Einschränkungen, Überprüfungen und Trigger enthält und einige dieser (Überprüfungen, die es besonders scheinen) während der Wiederherstellung fehlschlagen, da die Informationen nicht in der Reihenfolge geladen werden, in der diese Überprüfungen berücksichtigt würden. Zum Beispiel kann das Einfügen einer Zeile in eine Tabelle mit einer verknüpft sein CHECK, die eine plpgsqlFunktion aufruft , die prüft, ob eine Bedingung in einer anderen nicht verwandten Tabelle vorliegt. Wenn diese letztere Tabelle nicht psqlvor der ersteren geladen wird , tritt ein Fehler auf.

Das Folgende ist eine SSCCE, die eine solche Datenbank erstellt, die nach dem Sichern pg_dumpnicht wiederhergestellt werden kann:

CREATE OR REPLACE FUNCTION fail_if_b_empty () RETURNS BOOLEAN AS $$
    SELECT EXISTS (SELECT 1 FROM b)
$$ LANGUAGE SQL;

CREATE TABLE IF NOT EXISTS a (
     i              INTEGER                    NOT NULL
);

INSERT INTO a(i) VALUES (0),(1);
CREATE TABLE IF NOT EXISTS b (
    i  INTEGER NOT NULL
);
INSERT INTO b(i) VALUES (0);

ALTER TABLE a ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty());

Gibt es eine Möglichkeit, alle diese Einschränkungen während der Dump-Wiederherstellung (über die Befehlszeile) zu deaktivieren und anschließend wieder zu aktivieren? Ich verwende PostgreSQL 9.1.

Marcus Junius Brutus
quelle
Ich frage mich, AFAIK gibt es keine -Xund -dOptionen für pg_dump. pg_dumperzeugt ein Dump , die ist in einer leeren DB restorable.
Dezso
1
@dezso richtig, das waren Tippfehler, ich habe die Frage aktualisiert. Der Dump ist aus den von mir genannten Gründen leider nicht in einer leeren DB wiederherstellbar.
Marcus Junius Brutus
Die Frage braucht dringend Ihre Version von Postgres. Dies sollte offensichtlich sein, ohne dass ich darauf hinweise.
Erwin Brandstetter
@ErwinBrandstetter Ich kann das gleiche Problem auf 9.6 reproduzieren, siehe bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 für ein anderes Beispiel (realer, etwas größer, MWE)
mirabilos am
1
@mirabilos: Ich würde sagen: Wenn Sie eine Funktion verwenden, die auf andere Tabellen in einer CHECKEinschränkung verweist , werden alle Garantien ungültig, da dies nicht offiziell unterstützt wird, sondern nur toleriert wird. Aber die CHECKEinschränkung NOT VALIDzu erklären, hat in jeder Hinsicht für mich funktioniert. Es kann Eckfälle geben, die ich nie angerührt habe ...
Erwin Brandstetter

Antworten:

17

Sie schlagen also andere Tabellen in einer CHECKEinschränkung nach .

CHECKEinschränkungen sollen IMMUTABLEPrüfungen ausführen . Was für eine Reihe auf einmal als OK eingestuft wird, sollte jederzeit als OK eingestuft werden . So werden CHECKEinschränkungen im SQL-Standard definiert. Das ist auch der Grund für diese Einschränkung ( laut Dokumentation ):

Derzeit dürfen CHECKAusdrücke keine Unterabfragen enthalten und sich nicht auf andere Variablen als die Spalten der aktuellen Zeile beziehen.

Jetzt können Ausdrücke in CHECKEinschränkungen Funktionen verwenden, auch benutzerdefinierte Funktionen. Diese sollten auf IMMUTABLEFunktionen beschränkt sein, aber Postgres erzwingt dies derzeit nicht. Nach dieser verwandten Diskussion über pgsql-Hacker besteht ein Grund darin, Verweise auf die aktuelle Zeit zuzulassen, was nicht IMMUTABLEvon Natur aus ist.

Sie suchen jedoch nach Zeilen einer anderen Tabelle, was völlig im Widerspruch dazu steht, wie CHECKEinschränkungen funktionieren sollen. Ich bin nicht überrascht, dass pg_dumpdies nicht möglich ist.

Verschieben Sie Ihren Scheck in einer anderen Tabelle auf einen Trigger (das ist das richtige Werkzeug), und es sollte mit modernen Versionen von Postgres funktionieren.

PostgreSQL 9.2 oder höher

Während das oben Gesagte für jede Version von Postgres zutrifft, wurden mit Postgres 9.2 verschiedene Tools eingeführt, um in Ihrer Situation zu helfen:

Option pg_dump --exclude-table-data

Eine einfache Lösung wäre, die Datenbank ohne Daten für die verletzende Tabelle zu sichern mit:

--exclude-table-data=my_schema.my_tbl

Dann hängen Sie einfach die Daten für diese Tabelle am Ende des Dumps an:

--data-only --table=my_schema.my_tbl

Es kann jedoch zu Komplikationen mit anderen Einschränkungen am selben Tisch kommen. Es gibt eine noch bessere Lösung :

NOT VALID

Es gibt den NOT VALIDModifikator für Einschränkungen. Nur für FK-Constraints in Version 9.1 verfügbar, wurde jedoch in Version CHECK9.2 auf Constraints erweitert . Per Dokumentation:

Wenn die Einschränkung markiert ist NOT VALID, wird die möglicherweise langwierige Erstprüfung, um sicherzustellen, dass alle Zeilen in der Tabelle die Einschränkung erfüllen, übersprungen. Die Einschränkung wird weiterhin für nachfolgende Einfügungen oder Aktualisierungen erzwungen [...]

Eine einfache Postgres-Dump-Datei besteht aus drei "Abschnitten":

  • pre_data
  • data
  • post-data

In Postgres 9.2 wurde auch die Option eingeführt, Abschnitte separat mit -- section=sectionnameauszulagern, dies hilft jedoch nicht bei dem vorliegenden Problem.

Hier wird es interessant. Per Dokumentation:

Zu den Post-Data-Elementen gehören Definitionen von Indizes, Triggern, Regeln und Einschränkungen , die keine validierten Überprüfungsbedingungen sind . Vordatenelemente umfassen alle anderen Datendefinitionselemente.

Meine kühne Betonung.
Sie können die anstößige CHECKEinschränkung in ändern NOT VALID, wodurch die Einschränkung in den post-dataAbschnitt verschoben wird. Löschen und neu erstellen:

ALTER TABLE a DROP CONSTRAINT a_constr_1;
ALTER TABLE a ADD  CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()) NOT VALID;

Dies sollte Ihr Problem lösen. Sie können die Einschränkung sogar in diesem Zustand belassen , da dies die tatsächliche Funktion besser widerspiegelt: Überprüfen Sie neue Zeilen, geben Sie jedoch keine Garantie für vorhandene Daten. An einer Prüfbedingung ist nichts auszusetzen NOT VALID. Wenn Sie es vorziehen, können Sie es später validieren:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

Aber dann sind Sie wieder im Status Quo Ante.

Erwin Brandstetter
quelle
Ich habe die Frage mit einer SSCCE angereichert, die eine Datenbank anzeigt, die nicht wiederhergestellt werden kann. Ich verstehe, was Sie sagen, verstehe jedoch nicht, warum die problematische Situation, die ich in meiner SSCCE präsentiere, nicht auch mit Auslösern anstelle von Überprüfungen reproduziert werden kann.
Marcus Junius Brutus
1
@MarcusJuniusBrutus: Da Prüfungseinschränkungen für alle Zeilen ausgewertet werden, die sich beim Erstellen bereits in der Tabelle befinden, werden Trigger nur für definierte Ereignisse ausgeführt.
Erwin Brandstetter
Ich habe die exakte Schemalogik mithilfe von Triggern reproduziert. Mithilfe von Triggern ist die Wiederherstellung zwar erfolgreich, dies scheint jedoch nur auf die Tatsache zurückzuführen zu sein, dass die pg_dumpTrigger am Ende der Speicherauszugsdatei hinzugefügt werden, während das CHECKs als Teil des CREATE TABLEBefehls erstellt wird. Daher hätte die Wiederherstellung auch für den Überprüfungsfall erfolgreich sein können, wenn das pg_dumpTool einen anderen Ansatz verwendet hätte. Ich verstehe nicht, warum meine DDL in Ordnung ist, wenn ich Trigger verwende, aber nicht in Ordnung, wenn ich Prüfungen verwende, da in beiden Fällen genau dieselbe Logik implementiert ist (Sie können die Version des Skripts anhand von Triggern in meiner eigenen Antwort sehen).
Marcus Junius Brutus
1
@MarcusJuniusBrutus: Wenn Sie der Meinung pg_dumpsind, dass unterschiedliche DDLs für Prüfbeschränkungen generiert werden sollen (z. B. alle am Ende hinzufügen), sollten Sie diese als Erweiterungsanforderung in die Postgres-Mailingliste aufnehmen. Aber ich stimme Erwin zu, dass Sie Check-Beschränkungen für etwas missbrauchen, für das sie nicht entwickelt wurden. Daher würde ich nicht erwarten, dass diese Änderungsanforderung in naher Zukunft implementiert wird. Übrigens: Ihre SSCCE sollte besser mit einem Fremdschlüssel zwischen den beiden Tabellen modelliert werden.
a_horse_with_no_name
@MarcusJuniusBrutus: Es gibt Lösungen für Postgres 9.2 oder höher. Aus diesem Grund ist Ihre Version von Postgres von entscheidender Bedeutung. Vielleicht ist ein Upgrade eine Option für Sie? Postgres 9.1 altert sowieso ...
Erwin Brandstetter
2

Es scheint, dass dies an der Art pg_dumpund Weise liegt, wie der Dump erstellt wird. Als ich mir den tatsächlichen Speicherauszug ansah, sah ich, dass die CHECKEinschränkung in der Speicherauszugsdatei vorhanden war, und zwar mit der Syntax, die Teil des CREATE TABLEBefehls ist:

CREATE TABLE a (
    i integer NOT NULL,
    CONSTRAINT a_constr_1 CHECK (fail_if_b_empty())
);      

Dies führt beim Wiederherstellen der Datenbank zu einem Fehler, da die Prüfung durchgeführt wird, bevor entweder eine Tabelle aoder eine Tabelle bDaten enthält. Wenn jedoch die Dump-Datei bearbeitet und CHECKstattdessen mit der folgenden Syntax am Ende der Dump-Datei hinzugefügt wird:

ALTER TABLE a ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()); 

... dann ist die Restauration kein Problem.

Die exakt gleiche Logik kann mit a TRIGGERwie im folgenden Skript implementiert werden :

CREATE OR REPLACE FUNCTION fail_if_b_empty (
    ) RETURNS BOOLEAN AS $$
    SELECT EXISTS (SELECT 1 FROM b)
$$ LANGUAGE SQL;

DROP TABLE IF EXISTS a;

CREATE TABLE IF NOT EXISTS a (
    i   INTEGER   NOT NULL
);

INSERT INTO a(i) VALUES (0),(1);

CREATE TABLE IF NOT EXISTS b (
    i  INTEGER NOT NULL
);

INSERT INTO b(i) VALUES (0);

CREATE TRIGGER tr1 AFTER INSERT OR UPDATE ON a
FOR EACH ROW
EXECUTE PROCEDURE fail_if_b_empty();  

In diesem Fall wird jedoch pg_dump(standardmäßig) der Trigger am Ende der Speicherauszugsdatei erstellt (und nicht in der CREATE TABLEAnweisung wie im Fall einer Überprüfung), sodass die Wiederherstellung erfolgreich ist.

Marcus Junius Brutus
quelle
In Ihrem Beispiel ist kein Auslöser zu sehen
Sam Watkins,
1
@ SamWatkins Copy-Paste-Fehler, behoben.
Marcus Junius Brutus