Disabilita tutti i vincoli e i controlli della tabella durante il ripristino di un dump


19

Ho ottenuto un dump del mio database PostgreSQL con:

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

che quindi procedo al ripristino in un altro database con:

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

Il mio problema è che il database contiene vincoli, controlli e trigger referenziali e alcuni di questi (controlli sembrerebbero in particolare) falliscono durante il ripristino poiché le informazioni non vengono caricate nell'ordine che renderebbe tali controlli onorati. Ad esempio, l'inserimento di una riga in una tabella può essere associato a una CHECKche chiama una plpgsqlfunzione che controlla se una condizione è presente in un'altra tabella non correlata. Se quest'ultima tabella non viene caricata psqlprima della prima, si verifica un errore.

Di seguito è riportato un SSCCE che produce un tale database che una volta scaricato con pg_dumpnon può essere ripristinato:

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());

C'è un modo per disabilitare (dalla riga di comando) tutti questi vincoli durante il ripristino del dump e riattivarli in seguito? Sto eseguendo PostgreSQL 9.1.


Mi chiedo, AFAIK non ci sono -Xe -dopzioni per pg_dump. pg_dumpproduce una discarica che è ripristinabile in un DB vuoto.
dezso,

1
@dezso giusto, questi erano errori di battitura, ho aggiornato la domanda. Il dump purtroppo non è ripristinabile in un DB vuoto a causa dei motivi che sto citando.
Marco Giunio Bruto,

La domanda ha davvero bisogno della tua versione di Postgres. Questo dovrebbe essere ovvio senza che me lo sottolinei.
Erwin Brandstetter,

@ErwinBrandstetter Posso riprodurre lo stesso problema il 9.6, vedi bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 per un altro esempio (più reale, leggermente più grande, MWE)
mirabilos

1
@mirabilos: Direi: se usi una funzione che fa riferimento ad altre tabelle in un CHECKvincolo, allora tutte le garanzie vengono annullate, perché non ufficialmente supportate, solo tollerate. Ma dichiarare il CHECKvincolo NOT VALIDlo ha fatto funzionare per me sotto ogni aspetto. Potrebbero esserci casi angolari che non ho mai toccato ...
Erwin Brandstetter,

Risposte:


17

Quindi cerchi altre tabelle in un CHECKvincolo .

CHECKi vincoli dovrebbero eseguire IMMUTABLEcontrolli. Ciò che passa OK per una riga alla volta dovrebbe passare OK in qualsiasi momento. Ecco come CHECKvengono definiti i vincoli nello standard SQL. Questo è anche il motivo di questa restrizione ( per documentazione ):

Attualmente, le CHECKespressioni non possono contenere sottoquery né fare riferimento a variabili diverse dalle colonne della riga corrente.

Ora, alle espressioni nei CHECKvincoli è consentito utilizzare funzioni, anche funzioni definite dall'utente. Questi dovrebbero essere limitati alle IMMUTABLEfunzioni, ma Postgres non lo applica attualmente. Secondo questa discussione correlata sugli hacker di pgsql , uno dei motivi è quello di consentire riferimenti al tempo corrente, che non è IMMUTABLEper natura.

Ma stai cercando le righe di un'altra tabella, il che è completamente in violazione di come CHECKdovrebbero funzionare i vincoli. Non mi sorprende che pg_dumpnon riesca a provvedere a questo.

Sposta il segno di spunta in un'altra tabella su un trigger (che è lo strumento giusto) e dovrebbe funzionare con le versioni moderne di Postgres.

PostgreSQL 9.2 o successivo

Sebbene quanto sopra sia vero per qualsiasi versione di Postgres, Postgres 9.2 ha introdotto numerosi strumenti per aiutarti a risolvere la tua situazione:

opzione pg_dump --exclude-table-data

Una soluzione semplice sarebbe quella di scaricare il db senza dati per la tabella in violazione con:

--exclude-table-data=my_schema.my_tbl

Quindi aggiungi solo i dati per questa tabella alla fine del dump con:

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

Ma potrebbero derivarne complicazioni con altri vincoli sulla stessa tabella. C'è una soluzione ancora migliore :

NOT VALID

C'è il NOT VALIDmodificatore per i vincoli. Disponibile solo per il vincolo FK in v9.1, ma questo è stato esteso ai CHECKvincoli in 9.2. Per documentazione:

Se il vincolo è contrassegnato NOT VALID, viene saltato il controllo iniziale potenzialmente lungo per verificare che tutte le righe nella tabella soddisfino il vincolo. Il vincolo verrà comunque applicato contro inserimenti o aggiornamenti successivi [...]

Un semplice file di dump di Postgres è composto da tre "sezioni":

  • pre_data
  • data
  • post-data

Postgres 9.2 ha anche introdotto un'opzione per scaricare sezioni separatamente -- section=sectionname, ma ciò non aiuta il problema attuale.

Qui è dove diventa interessante. Per documentazione:

Gli elementi post-dati includono definizioni di indici, trigger, regole e vincoli diversi dai vincoli di controllo convalidati . Gli elementi pre-dati includono tutti gli altri elementi di definizione dei dati.

Enorme enfasi sulla mia.
È possibile modificare il CHECKvincolo offensivo in NOT VALID, che sposta il vincolo nella post-datasezione. Rilascia e ricrea:

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

Questo dovrebbe risolvere il tuo problema. Puoi anche lasciare il vincolo in quello stato , poiché ciò riflette meglio ciò che effettivamente fa: controlla le nuove righe, ma non fornisce garanzie per i dati esistenti. Non c'è nulla di sbagliato in un NOT VALIDvincolo di controllo. Se preferisci, puoi convalidarlo in seguito:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

Ma poi torni allo status quo ante.


Ho arricchito la domanda con un SSCCE che mostra un database che non può essere ripristinato. Capisco quello che stai dicendo, tuttavia non vedo perché la situazione problematica che mostro nel mio SSCCE non possa essere riprodotta anche con i trigger invece dei controlli.
Marcus Junius Brutus,

1
@MarcusJuniusBrutus: poiché i vincoli di controllo vengono valutati per tutte le righe già presenti nella tabella al momento della creazione, mentre i trigger vengono eseguiti solo su eventi definiti.
Erwin Brandstetter,

Ho riprodotto l'esatta logica dello schema usando i trigger. Utilizzando i trigger, il ripristino ha effettivamente esito positivo, ma sembra che ciò sia dovuto solo al fatto che pg_dumpaggiunge i trigger alla fine del file di dump, mentre crea la CHECKs come parte del CREATE TABLEcomando. Quindi il restauro avrebbe potuto avere successo anche per il caso di controllo se lo pg_dumpstrumento avesse utilizzato un approccio diverso. Non riesco a capire perché il mio DDL sia OK se utilizzo i trigger ma non OK se utilizzo i controlli poiché in entrambi i casi viene implementata la stessa logica esatta (puoi vedere la versione dello script usando i trigger nella mia risposta).
Marcus Junius Brutus,

1
@MarcusJuniusBrutus: se ritieni che pg_dumpdebba generare DDL diversi per vincoli di controllo (ad es. Aggiungendoli tutti alla fine), dovresti pubblicarli nella mailing list di Postgres come richiesta di miglioramento. Ma sono d'accordo con Erwin sul fatto che stai usando male i vincoli di controllo per qualcosa per cui non sono stati progettati. Quindi non mi aspetto che la richiesta di modifica venga implementata nel prossimo futuro. A proposito: il tuo SSCCE sarebbe meglio modellato usando una chiave esterna tra le due tabelle.
a_horse_with_no_name

@MarcusJuniusBrutus: esistono soluzioni per Postgres 9.2 o successive. Ecco perché la tua versione di Postgres è cruciale. Forse l'aggiornamento è un'opzione per te? Postgres 9.1 invecchia comunque ...
Erwin Brandstetter,

2

Sembra che ciò sia dovuto al modo in cui pg_dumpcrea la discarica. Guardando il dump reale ho visto che il CHECKvincolo era presente nel file di dump usando la sintassi che fa parte del CREATE TABLEcomando:

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

Ciò crea l'errore al ripristino del database quando viene eseguito il controllo prima che una tabella ao una tabella bcontenga dei dati. Se tuttavia, il file di dump viene modificato e CHECKviene invece aggiunto utilizzando la sintassi seguente, alla fine del file di dump:

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

... quindi non ci sono problemi nel restauro.

La stessa identica logica può essere implementata usando a TRIGGERcome nel seguente script:

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 questo caso, tuttavia, pg_dumpcrea (per impostazione predefinita) il trigger alla fine del file di dump (e non CREATE TABLEnell'istruzione come nel caso di un controllo) e quindi il ripristino ha esito positivo.


non vedo alcun trigger nel tuo esempio
Sam Watkins,

1
Errore di copia-incolla di @SamWatkins, risolto.
Marcus Junius Brutus il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.