Der beste Weg, um Millionen von Zeilen nach ID zu löschen

76

Ich muss ungefähr 2 Millionen Zeilen aus meiner PG-Datenbank löschen. Ich habe eine Liste von IDs, die ich löschen muss. Jeder Versuch, dies zu tun, dauert jedoch Tage.

Ich habe versucht, sie in eine Tabelle zu stellen und dies in Stapeln von 100 zu tun. 4 Tage später läuft dies immer noch mit nur 297268 gelöschten Zeilen. (Ich musste 100 IDs aus einer ID-Tabelle auswählen, wo IN dieser Liste löschen, die 100, die ich ausgewählt habe, aus der IDs-Tabelle löschen).

Ich habe es versucht:

DELETE FROM tbl WHERE id IN (select * from ids)

Das dauert auch ewig. Schwer einzuschätzen, wie lange es dauert, da ich den Fortschritt erst nach Abschluss sehen kann, aber die Abfrage nach 2 Tagen noch ausgeführt wurde.

Ich suche nur nach dem effektivsten Weg, um aus einer Tabelle zu löschen, wenn ich die zu löschenden spezifischen IDs kenne und es Millionen von IDs gibt.

Anthony Greco
quelle
2
Wie viele Zeilen bleiben übrig? Eine Alternative wäre, die verbleibenden Zeilen in einer Arbeitstabelle auszuwählen und dann die Tabellen umzubenennen.
Thilo

Antworten:

96

Es hängt alles ab ...

  • Löschen Sie alle Indizes (mit Ausnahme derjenigen auf der ID, die Sie zum Löschen benötigen).
    Erstellen Sie sie anschließend neu (= viel schneller als inkrementelle Aktualisierungen von Indizes).

  • Überprüfen Sie, ob Sie Trigger haben, die sicher vorübergehend gelöscht / deaktiviert werden können

  • Verweisen Fremdschlüssel auf Ihre Tabelle? Können sie gelöscht werden? Vorübergehend gelöscht?

  • Abhängig von Ihren Autovakuumeinstellungen kann es hilfreich sein, VACUUM ANALYZEvor der Operation zu laufen .

  • Angenommen, Sie haben keinen gleichzeitigen Schreibzugriff auf die beteiligten Tabellen, oder Sie müssen möglicherweise ausschließlich Tabellen sperren, oder diese Route ist möglicherweise überhaupt nicht für Sie.

  • Einige der Punkte, die im entsprechenden Kapitel des Handbuchs zum Auffüllen einer Datenbank aufgeführt sind, können je nach Einrichtung ebenfalls von Nutzen sein.

  • Wenn Sie große Teile der Tabelle löschen und der Rest in den Arbeitsspeicher passt, ist dies am schnellsten und einfachsten:

SET temp_buffers = '1000MB'; -- or whatever you can spare temporarily

CREATE TEMP TABLE tmp AS
SELECT t.*
FROM   tbl t
LEFT   JOIN del_list d USING (id)
WHERE  d.id IS NULL;      -- copy surviving rows into temporary table

TRUNCATE tbl;             -- empty table - truncate is very fast for big tables

INSERT INTO tbl
SELECT * FROM tmp;        -- insert back surviving rows.

Auf diese Weise müssen Sie keine Ansichten, Fremdschlüssel oder andere abhängige Objekte neu erstellen. Lesen Sie mehr über die temp_buffersEinstellung im Handbuch . Diese Methode ist schnell, solange die Tabelle in den Speicher passt oder zumindest größtenteils. Beachten Sie, dass Sie Daten verlieren können, wenn Ihr Server während dieses Vorgangs abstürzt. Sie können alles in eine Transaktion einbinden, um sie sicherer zu machen.

ANALYZEDanach laufen . Oder VACUUM ANALYZEwenn Sie die abgeschnittene Route nicht gewählt haben oder VACUUM FULL ANALYZEwenn Sie sie auf die minimale Größe bringen möchten. Berücksichtigen Sie bei großen Tischen die Alternativen CLUSTER/ pg_repack:

Bei kleinen Tischen ist ein einfaches DELETEstatt TRUNCATEoft schneller:

DELETE FROM tbl t
USING  del_list d
WHERE  t.id = d.id;

Lesen Sie den Abschnitt NotizenTRUNCATE im Handbuch . Insbesondere (wie Pedro auch in seinem Kommentar betonte ):

TRUNCATEkann nicht für eine Tabelle verwendet werden, die Fremdschlüsselreferenzen aus anderen Tabellen enthält, es sei denn, alle diese Tabellen werden im selben Befehl ebenfalls abgeschnitten. [...]

Und:

TRUNCATEON DELETElöst keine Trigger aus, die möglicherweise für die Tabellen vorhanden sind.

Erwin Brandstetter
quelle
Leider habe ich ein paar Fremdschlüssel, aber ich kann das tun, was Sie vorgeschlagen haben, indem ich alle Schlüssel töte / lösche / neu erstelle. Es braucht mehr Zeit, dies nicht zu tun, als es einfach zu tun. Vielen Dank!
Anthony Greco
@AnthonyGreco: Sie können die Fremdschlüssel löschen und anschließend neu erstellen. Natürlich müssen Sie sich auch um Verweise auf gelöschte Zeilen kümmern. Die referenzielle Integrität wird in diesem Fenster nicht garantiert.
Erwin Brandstetter
1
Sicherlich war es nicht das, was ich tun wollte, aber das Löschen der Indizes ließ meine Löschvorgänge jetzt fliegen ... Jetzt muss ich das nur für alle verknüpften Tabellen tun, um verknüpfte Zeilen zu löschen, aber die Hölle schlägt die ganze Zeit, die ich damit verbracht habe, es zum Laufen zu bringen ohne
Anthony Greco
1
@AnthonyGreco: Cool! Vergessen Sie nicht, die noch benötigten Indizes anschließend neu zu erstellen.
Erwin Brandstetter
1
Dies ist eine großartige Lösung, würde nur hinzufügen, dass Löschkaskaden ignoriert werden, wenn dies für jemanden nicht offensichtlich ist.
Pedro Borges
4

Wir wissen, dass die Aktualisierungs- / Löschleistung von PostgreSQL nicht so leistungsfähig ist wie die von Oracle. Wenn wir Millionen oder Zehntausende von Millionen Zeilen löschen müssen, ist das wirklich schwierig und dauert lange.

Wir können dies jedoch immer noch in der Produktion tun. Folgendes ist meine Idee:

Zuerst sollten wir eine Protokolltabelle mit 2 Spalten erstellen - id& flag( idbezieht sich auf die ID, die Sie löschen möchten; flagkann sein Yoder nullkann Ybedeuten, dass der Datensatz erfolgreich gelöscht wurde).

Später erstellen wir eine Funktion. Wir erledigen die Löschaufgabe alle 10.000 Zeilen. Weitere Details finden Sie in meinem Blog . Obwohl es auf Chinesisch ist, können Sie die gewünschten Informationen dennoch aus dem dortigen SQL-Code abrufen.

Stellen Sie sicher, dass die idSpalte beider Tabellen Indizes sind, da sie schneller ausgeführt werden.

Franken
quelle
Nun, ich habe im Grunde eine Logik dafür gemacht, um es im Batch zu machen, aber es hat aufgrund meiner Indizes viel zu lange gedauert. Ich habe endlich alle meine Indizes gelöscht (was ich nicht wollte) und die Zeilen wurden verdammt schnell gelöscht. Jetzt werden alle meine Indizes wieder aufgebaut. Trotzdem danke!
Anthony Greco
2

Sie können versuchen, alle Daten in der Tabelle mit Ausnahme der IDs, die Sie löschen möchten, in eine neue Tabelle zu kopieren , sie dann umzubenennen und die Tabellen auszutauschen (vorausgesetzt, Sie verfügen über genügend Ressourcen, um dies zu tun).

Dies ist kein Expertenrat.

Saulius Žemaitaitis
quelle
Abhängig von der Anzahl der beizubehaltenden Zeilen und der Schwierigkeit anderer Fremdschlüssel kann dies funktionieren. Kann auch gute Zeilen auf temp kopieren. Aktuelle Tabelle abschneiden. Dann kopieren Sie von temp zurück.
nclu
2

Zwei mögliche Antworten:

  1. Ihre Tabelle enthält möglicherweise viele Einschränkungen oder Auslöser, wenn Sie versuchen, einen Datensatz zu löschen. Es werden viele Prozessorzyklen und Überprüfungen anhand anderer Tabellen erforderlich sein.

  2. Möglicherweise müssen Sie diese Anweisung in eine Transaktion einfügen.

Zaldy Baguinon
quelle
1. Ich habe Einschränkungen (Fremdschlüssel), die automatisch gelöscht werden, wenn eine Zeile in der Tabelle gelöscht wird
Anthony Greco
Versuchen Sie explain (analyze,buffers,timing) ...herauszufinden, welche Indizes Ihnen fehlen.
Mikko Rantalainen
2

Stellen Sie zunächst sicher, dass Sie einen Index für die ID-Felder haben, sowohl in der Tabelle, aus der Sie löschen möchten, als auch in der Tabelle, die Sie zum Löschen von IDs verwenden.

100 auf einmal scheint zu klein. Versuchen Sie 1000 oder 10000.

Es ist nicht erforderlich, etwas aus der Lösch-ID-Tabelle zu löschen. Fügen Sie eine neue Spalte für eine Chargennummer hinzu und füllen Sie sie mit 1000 für Charge 1, 1000 für Charge 2 usw. und stellen Sie sicher, dass die Löschabfrage die Chargennummer enthält.

Mark Ransom
quelle
2
Es stellte sich heraus, egal was ich versuchte, es waren die Schlüssel, die mich umbrachten. Sogar nur 15 dauerten ungefähr eine Minute, deshalb habe ich nur 100 gemacht. Nachdem ich den Index getötet hatte, flog er. Trotzdem danke!
Anthony Greco
1

Der einfachste Weg, dies zu tun, besteht darin, alle Ihre Einschränkungen zu löschen und dann das Löschen durchzuführen.

Vincent Agnello
quelle
Ich versuche wirklich, dies zu vermeiden, weil ich dann nur den Vorgang für alle Fremdschlüssel wiederholen muss, aber ich muss es möglicherweise sehr gut. Vielen Dank
Anthony Greco
1

Ich habe dieses Problem nur selbst gelöst und für mich war die mit Abstand schnellste Methode die Verwendung von WITH Queries in Kombination mit USING

Grundsätzlich erstellt die WITH-Abfrage eine temporäre Tabelle mit den zu löschenden Primärschlüsseln in der Tabelle, aus der Sie löschen möchten.

WITH to_delete AS (
   SELECT item_id FROM other_table WHERE condition_x = true
)
DELETE FROM table 
USING to_delete 
WHERE table.item_id = to_delete.item_id 
  AND NOT to_delete.item_id IS NULL;

Natürlich kann das SELECTInnere der WITH-Abfrage so komplex sein wie jede andere Auswahl mit mehreren Verknüpfungen usw. Es müssen nur eine oder mehrere Spalten zurückgegeben werden, mit denen die Elemente in der Zieltabelle identifiziert werden, die gelöscht werden müssen.

HINWEIS : AND NOT to_delete.item_id IS NULLHöchstwahrscheinlich ist dies nicht erforderlich, aber ich habe es nicht gewagt, es zu versuchen.

Andere Dinge zu beachten sind

  1. Erstellen von Indizes für andere Tabellen, die über einen Fremdschlüssel auf diese verweisen . Dies kann in bestimmten Situationen das Löschen von Stunden auf nur Sekunden reduzieren
  2. Constraint prüft aufzuschieben : Es ist nicht klar , wie viel, wenn eine Verbesserung Dadurch wird erreicht, aber nach dieser es die Leistung steigern kann. Nachteil ist, wenn Sie eine Fremdschlüsselverletzung haben, werden Sie diese erst im allerletzten Moment erfahren.
  3. GEFÄHRLICHER, aber großer möglicher Schub: Deaktivieren Sie Konstantenprüfungen und -auslöser während des Löschvorgangs
Torge
quelle
Sie können sogar mehrere solcher Tabellen erstellen, die aufeinander verweisen, wie ich es in einem Fall tun musste, in dem ich alle Zeilen löschen wollte, die Waisen waren und von keiner anderen Tabelle mehr referenziert wurden. ( WITH existing_items AS ( ... ), to_delete AS ( SELECT item_id FROM table LEFT JOIN existing_items e ON table.item_id = e.item_id WHERE e.item_id IS NULL ) DELETE FROM ...)
Torge
0

Wenn auf die Tabelle, aus der Sie löschen, verwiesen wird some_other_table(und Sie die Fremdschlüssel nicht einmal vorübergehend löschen möchten), stellen Sie sicher, dass Sie einen Index für die Referenzierungsspalte in haben some_other_table!

Ich hatte ein ähnliches Problem und benutzte es auto_explainmit auto_explain.log_nested_statements = true, was ergab, dass das deletetatsächlich seq_scans auf some_other_table:

    Query Text: SELECT 1 FROM ONLY "public"."some_other_table" x WHERE $1 OPERATOR(pg_catalog.=) "id" FOR KEY SHARE OF x    
    LockRows  (cost=[...])  
      ->  Seq Scan on some_other_table x  (cost=[...])  
            Filter: ($1 = id)

Anscheinend wird versucht, die referenzierenden Zeilen in der anderen Tabelle zu sperren (die nicht existieren sollten, sonst schlägt das Löschen fehl). Nachdem ich Indizes für die Referenzierungstabellen erstellt hatte, war das Löschen um Größenordnungen schneller.

FunctorSalad
quelle