Sehr langsames LÖSCHEN in PostgreSQL, Problemumgehung?

30

Ich habe eine Datenbank auf PostgreSQL 9.2, die ein Hauptschema mit ungefähr 70 Tabellen und eine variable Anzahl von identischen Schemata pro Client mit jeweils 30 Tabellen enthält. Die Client-Schemas haben Fremdschlüssel, die auf das Hauptschema verweisen und nicht umgekehrt.

Ich habe gerade angefangen, die Datenbank mit echten Daten aus der Vorgängerversion zu füllen. Die Datenbank hatte ungefähr 1,5 GB erreicht (es wird erwartet, dass sie innerhalb weniger Wochen auf mehrere 10 GB anwächst), als ich in einer sehr zentralen Tabelle im Hauptschema einen Massenlöschvorgang durchführen musste. Alle betroffenen Fremdschlüssel sind mit ON DELETE CASCADE gekennzeichnet.

Es war keine Überraschung, dass dies lange dauern würde, aber nach 12 Stunden wurde klar, dass es mir besser geht, von vorne zu beginnen, die Datenbank zu löschen und die Migration erneut zu starten. Aber was ist, wenn ich diesen Vorgang später wiederholen muss, wenn die Datenbank aktiv und viel größer ist? Gibt es alternative, schnellere Methoden?

Wäre es viel schneller, wenn ich ein Skript schreiben würde, das die abhängigen Tabellen durchsucht, beginnend mit der Tabelle, die am weitesten von der zentralen Tabelle entfernt ist, und die abhängigen Zeilen tabellenweise löscht?

Ein wichtiges Detail ist, dass einige Tabellen Trigger enthalten.

jd.
quelle
4
Nach 5 Jahren ändere ich die akzeptierte Antwort. Langsame DELETEs werden fast immer durch fehlende Indizes für Fremdschlüssel verursacht, die direkt oder indirekt auf die Tabelle verweisen, aus der gelöscht wird. Auslöser, die bei DELETE-Anweisungen ausgelöst werden, können ebenfalls die Ausführung verlangsamen. Die Lösung besteht jedoch fast immer darin, die Ausführung zu beschleunigen (z. B. durch Hinzufügen fehlender Indizes) und fast nie alle Auslöser zu deaktivieren.
jd.

Antworten:

28

Ich hatte ein ähnliches Problem. Wie sich herausstellte, ON DELETE CASCADEverlangsamten diese Auslöser die Geschwindigkeit erheblich, da diese kaskadierten Löschvorgänge furchtbar langsam waren.

Ich habe das Problem gelöst, indem ich Indizes für die Fremdschlüsselfelder in den Verweistabellen erstellt habe, und ich habe ein paar Stunden für das Löschen in ein paar Sekunden investiert.

ailnlv
quelle
Wow, das hat mir geholfen, 8 Millionen Datensätze in wenigen Minuten zu löschen. Was ich aber nicht verstehe, ist, dass meine Tabelle nur Verweise auf andere Tabellen enthielt, keine anderen Tabellen enthalten Verweise auf meine Tabelle. Wie genau wirkt sich das hier aus? (Ich benutze nicht ON DELETE CASCADE)
msrd0
2
Dies löste es auch für mich. Für jeden, der dies versucht, können Sie eine EXPLAIN (ANALYZE, BUFFERS)Abfrage zum Löschen einer einzelnen Zeile durchführen. Dabei sollte angezeigt werden, welche Fremdschlüsseleinschränkungen am längsten gedauert haben (zumindest für mich).
Justin Workman
Dasselbe musste auf Kaskaden-600k-Zeilen löschen und am Anfang dauerte es zwischen 2-10 pro Operation mit 100% CPU-Auslastung. Jetzt dauerte es nur noch wenige Minuten, um alle zu löschen, bei einer CPU-Auslastung von 80%.
Fillobotto
Es ist wichtig zu beachten, dass die Quellenspalte einen echten Index haben muss , wenn Sie irgendwo einen Fremdverweis haben, sonst leidet die Leistung. Ich bin mir nicht sicher, ob der PRIMARYIndex ausreicht, aber der UNIQUEIndex ist definitiv nicht gut genug für diesen Zweck.
Mikko Rantalainen
26

Sie haben ein paar Möglichkeiten. Die beste Option ist das Ausführen eines Batch-Löschvorgangs, damit die Trigger nicht getroffen werden. Deaktivieren Sie die Trigger vor dem Löschen und aktivieren Sie sie dann erneut. Das spart Ihnen sehr viel Zeit. Beispielsweise:

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

Ein wichtiger Schlüssel hierbei ist, dass Sie die Tiefe der Unterabfragen minimieren möchten. In diesem Fall möchten Sie möglicherweise temporäre Tabellen einrichten, um relevante Informationen zu speichern, damit Sie tiefe Unterabfragen zum Löschen vermeiden können.

Chris Travers
quelle
In meinem Fall habe ich den DELETE FROM-Befehl vor dem Zubettgehen gestartet und er wurde immer noch nicht ausgeführt, als ich am nächsten Tag zu meinem Computer zurückkehrte. 100% CPU-Auslastung auf einem Kern die ganze Zeit. Nach dem Deaktivieren der Trigger und erneutem Versuch dauerte es 3 Sekunden, bis 200.000 Datensätze gelöscht waren. Vielen Dank!
Nick Woodhams
13

Die einfachste Methode , um das Problem zu lösen , ist ein detailliertes Timing von den PostgreSQL abfragen: EXPLAIN. Dazu müssen Sie mindestens eine einzelne Abfrage finden, die vollständig ist, aber länger als erwartet dauert. Nehmen wir an, diese Zeile würde so aussehen

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

Anstatt diesen Befehl wirklich auszuführen, können Sie dies tun

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

Das Rollback am Ende ermöglicht es, dies auszuführen, ohne die Datenbank wirklich zu ändern, aber Sie erhalten immer noch das detaillierte Timing, was wie viel gekostet hat. Nachdem Sie das ausgeführt haben, stellen Sie möglicherweise in der Ausgabe fest, dass ein Trigger große Verzögerungen verursacht:

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

Die timeAngabe erfolgt in ms (Millisekunden), sodass die Überprüfung dieser Einschränkung ca. 12,3 Sekunden dauerte. Sie müssen INDEXüber die erforderlichen Spalten eine neue hinzufügen, damit dieser Trigger effektiv berechnet werden kann. Bei Fremdschlüsselreferenzen muss die Spalte, die auf eine andere Tabelle verweist, indiziert werden (dh die Quellenspalte, nicht die Zielspalte). PostgreSQL erstellt solche Indizes nicht automatisch für Sie und DELETEist die einzige häufig verwendete Abfrage, bei der Sie diesen Index wirklich wirklich benötigen. Infolgedessen haben Sie möglicherweise jahrelange Daten gesammelt, bis Sie auf den Fall DELETEstoßen, dass aufgrund des Fehlens eines Indexes zu langsam ist.

Wiederholen Sie den Befehl in begin/ rollbackblock, wenn Sie die Leistung dieser Einschränkung behoben haben (oder etwas anderes, das zu lange gedauert hat), damit Sie die neue Ausführungszeit mit der vorherigen vergleichen können. Fahren Sie fort, bis Sie mit der Antwortzeit für das Löschen einzelner Zeilen zufrieden sind (ich habe eine Abfrage von 25,6 Sekunden auf 15 ms, indem ich einfach verschiedene Indizes hinzufüge). Anschließend können Sie den Löschvorgang ohne Hacks abschließen.

(Beachten Sie, dass EXPLAINeine Abfrage erforderlich ist, die erfolgreich abgeschlossen werden kann. Ich hatte einmal ein Problem, bei dem PostgreSQL zu lange EXPLAINgebraucht hat, um herauszufinden, dass ein Löschvorgang eine Fremdschlüsseleinschränkung verletzt und in diesem Fall nicht verwendet werden kann, da er kein Timing für fehlgeschlagen ausgibt Ich kenne keinen einfachen Weg, um in einem solchen Fall Leistungsprobleme zu beheben.)

Mikko Rantalainen
quelle
8

Das Deaktivieren von Triggern kann eine Bedrohung für die Datenbankintegrität darstellen und kann nicht empfohlen werden. Wenn Sie jedoch sicher sind, dass Ihre Operation einschränkungssicher ist, können Sie Trigger wie folgt deaktivieren:SET session_replication_role = replica;

Führen Sie die DELETEhier.

Führen Sie zum Wiederherstellen von Triggern Folgendes aus: SET session_replication_role = DEFAULT;

Quelle hier.

Pinimo
quelle
0

Wenn Sie ON DELETE CASCADE-Trigger haben, sind diese hoffentlich aus einem bestimmten Grund vorhanden und sollten daher nicht deaktiviert werden. Ein weiterer Trick (fügen Sie noch Ihre Indizes hinzu), der für mich funktioniert, besteht darin, eine Löschfunktion zu erstellen, mit der Daten, die mit den Tabellen am Ende der Kaskade beginnen, manuell gelöscht werden und in Richtung der Haupttabelle ausgeführt werden. (Dies ist dasselbe, was Sie tun müssten, wenn Sie einen ON DELETE RESTRICT-Trigger hätten.)

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 diesem Fall löschen Sie die Daten in tabelle c, tabelle b und tabelle a

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

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

$$ LANGUAGE SQL;
blindguy
quelle