ELIMINA molto lenta in PostgreSQL, soluzione alternativa?


30

Ho un database su PostgreSQL 9.2 che ha uno schema principale con circa 70 tabelle e un numero variabile di schemi per client strutturati in modo identico di 30 tabelle ciascuno. Gli schemi client hanno chiavi esterne che fanno riferimento allo schema principale e non viceversa.

Ho appena iniziato a riempire il database con alcuni dati reali presi dalla versione precedente. Il DB aveva raggiunto circa 1,5 GB (si prevede che raggiungesse i 10 GB entro alcune settimane) quando ho dovuto eseguire una cancellazione di massa in una tabella molto centrale nello schema principale. Tutte le chiavi esterne interessate sono contrassegnate ON ELIMINA CASCATA.

Non è stata una sorpresa che ci sarebbe voluto molto tempo, ma dopo 12 ore è diventato chiaro che stavo meglio ricominciando, lasciando il DB e riavviando la migrazione. Ma cosa succede se devo ripetere questa operazione in seguito quando il DB è attivo e molto più grande? Esistono metodi alternativi e più veloci?

Sarebbe molto più veloce se scrivessi uno script che sfoglierà le tabelle dipendenti, iniziando dalla tabella più lontana dalla tabella centrale, eliminando le righe dipendenti tabella per tabella?

Un dettaglio importante è che ci sono trigger su alcune delle tabelle.


4
Dopo 5 anni, sto cambiando la risposta accettata. Le DELETE lente sono quasi sempre causate da indici mancanti su chiavi esterne che fanno riferimento direttamente o indirettamente alla tabella da cui si elimina. I trigger che attivano le istruzioni DELETE possono anche rallentare le cose, sebbene la soluzione sia quasi sempre quella di farle funzionare più velocemente (ad esempio aggiungendo indici mancanti) e quasi mai di disabilitare tutti i trigger.
jd.

Risposte:


30

Ho avuto un problema simile. A quanto pare, quei ON DELETE CASCADEfattori scatenanti rallentavano un po 'le cose, perché quelle eliminazioni a cascata erano terribilmente lente.

Ho risolto il problema creando indici sui campi chiave esterna nelle tabelle di riferimento e sono passato da un paio d'ore per la cancellazione a pochi secondi.


Wow, questo mi ha aiutato a eliminare i record 8M in pochi minuti. Ma quello che non capisco è che la mia tabella conteneva solo riferimenti ad altre tabelle, nessun'altra tabella contiene riferimenti alla mia tabella. Quindi qual è esattamente l'effetto qui? (Non sto usando ON DELETE CASCADE)
msrd0

2
Questo ha risolto anche per me. Per chiunque provi questo, puoi fare una EXPLAIN (ANALYZE, BUFFERS)query su una singola riga di eliminazione e dovrebbe mostrarti quali vincoli di chiave esterna hanno impiegato più tempo (almeno per me).
Justin Workman,

Lo stesso, ha dovuto eliminare su 600k file in cascata e all'inizio impiegava tra il 2 e il 10 per operazione con il 100% di utilizzo della CPU. Ora ci sono voluti solo pochi minuti per eliminarli tutti con un utilizzo della CPU dell'80%.
Fillobotto,

È importante notare che se si ha un riferimento esterno a un punto qualsiasi, la colonna di origine deve avere un indice reale o le prestazioni ne risentiranno. Non sono sicuro che l' PRIMARYindice sia sufficiente, ma l' UNIQUEindice non è sicuramente abbastanza buono per questo scopo.
Mikko Rantalainen,

26

Hai alcune opzioni. L'opzione migliore è eseguire un'eliminazione batch in modo che i trigger non vengano premuti. Disabilitare i trigger prima di eliminarli, quindi riattivarli. Questo ti fa risparmiare molto tempo. Per esempio:

ALTER TABLE tablename DISABLE TRIGGER ALL; 
DELETE ...; 
ALTER TABLE tablename ENABLE TRIGGER ALL;

Una chiave importante qui è che vuoi ridurre al minimo la profondità delle sottoquery. In questo caso, potresti voler impostare tabelle temporanee per archiviare informazioni pertinenti in modo da evitare subquery profonde sulla tua eliminazione.


Nel mio caso, ho avviato il comando DELETE FROM prima di andare a letto e non è stato ancora eseguito quando sono tornato sul mio computer il giorno successivo. 100% di utilizzo della CPU su un core per tutto il tempo. Dopo aver disabilitato i trigger e riprovato, sono stati necessari 3 secondi per eliminare 200.000 record. Grazie!
Nick Woodhams,

13

Il metodo più semplice per risolvere il problema è quello di interrogare temporizzazione dettagliato dal PostgreSQL: EXPLAIN. Per questo è necessario trovare almeno una singola query che completa ma richiede più tempo del previsto. Diciamo che questa linea sarebbe simile

delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';

Invece di eseguire davvero quel comando, puoi farlo

begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;

Il rollback alla fine consente di eseguirlo senza modificare realmente il database ma si ottiene comunque il tempismo dettagliato di ciò che ha richiesto quanto. Dopo averlo eseguito, è possibile che nell'output alcuni trigger causino enormi ritardi:

...
Trigger for constraint XYZ123: time=12311.292 calls=1
...

Il valore timeè in ms (millisecondi), quindi il controllo di questa contesa ha richiesto circa 12,3 secondi. È necessario aggiungere un nuovo INDEXsopra le colonne richieste in modo che questo trigger possa essere calcolato in modo efficace. Per i riferimenti a chiave esterna, la colonna che fa riferimento a un'altra tabella deve essere indicizzata (ovvero, la colonna di origine, non la colonna di destinazione). PostgreSQL non crea automaticamente tali indici per te ed DELETEè l'unica query comune in cui hai davvero bisogno di quell'indice. Di conseguenza, potresti aver accumulato anni di dati fino a quando non raggiungi il caso in cui DELETEè troppo lento a causa della mancanza di un indice.

Dopo aver corretto le prestazioni di quel vincolo (o qualche altra cosa che ha richiesto molto tempo), ripetere il comando in begin/ rollbackblock in modo da poter confrontare il nuovo tempo di esecuzione con il precedente. Continua fino a quando non sei soddisfatto del tempo di risposta per l'eliminazione di una riga (ho una query da 25,6 secondi a 15 ms semplicemente aggiungendo indici diversi). Quindi puoi procedere per completare la cancellazione completa senza alcun hack.

(Nota che ha EXPLAINbisogno di una query che possa essere completata con successo. Una volta ho avuto un problema in cui PostgreSQL impiegava troppo tempo a capire che un'eliminazione avrebbe violato un vincolo di chiave esterna e in quel caso EXPLAINnon poteva essere utilizzata perché non emetteva tempistiche per errori interrogazioni. In questo caso non conosco alcun modo semplice per eseguire il debug di problemi di prestazioni.)


8

La disabilitazione dei trigger può costituire una minaccia per l'integrità del DB e non può essere consigliata; tuttavia, se si è certi che l'operazione sia a prova di errore, è possibile disabilitare i trigger con quanto segue:SET session_replication_role = replica;

Esegui il DELETEqui.

Per ripristinare i trigger, eseguire: SET session_replication_role = DEFAULT;

Fonte qui.


0

Se hai i trigger ON DELETE CASCADE, si spera che siano lì per un motivo e quindi non dovrebbero essere disabilitati. Un altro trucco (aggiungi ancora i tuoi indici) che funziona per me è quello di creare una funzione di eliminazione che elimini manualmente i dati a partire dalle tabelle alla fine della cascata e lavori verso la tabella principale. (È lo stesso che dovresti fare se avessi un trigger ON DELETE RESTRICT)

CREATE TABLE tablea (
    tablea_uid integer
);

CREATE TABLE tableb (
    tableb_uid integer,
    tablea_rid integer REFERENCES tablea(tablea_uid)
);

CREATE TABLE tablec (
    tablec_uid integer,
    tableb_rid integer REFERENCES tableb(tableb_uid)
);

In questo caso, eliminare i dati in tablec, quindi tableb, quindi tablea

CREATE OR REPLACE FUNCTION delete_in_order()
 RETURNS void AS $$

    DELETE FROM tablec;
    DELETE FROM tableb;
    DELETE FROM tablea;

$$ LANGUAGE SQL;
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.