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.
quelle
Antworten:
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 ANALYZE
vor 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_buffers
Einstellung 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.ANALYZE
Danach laufen . OderVACUUM ANALYZE
wenn Sie die abgeschnittene Route nicht gewählt haben oderVACUUM FULL ANALYZE
wenn Sie sie auf die minimale Größe bringen möchten. Berücksichtigen Sie bei großen Tischen die AlternativenCLUSTER
/pg_repack
:Bei kleinen Tischen ist ein einfaches
DELETE
stattTRUNCATE
oft schneller:DELETE FROM tbl t USING del_list d WHERE t.id = d.id;
Lesen Sie den Abschnitt Notizen
TRUNCATE
im Handbuch . Insbesondere (wie Pedro auch in seinem Kommentar betonte ):Und:
quelle
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
(id
bezieht sich auf die ID, die Sie löschen möchten;flag
kann seinY
odernull
kannY
bedeuten, 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
id
Spalte beider Tabellen Indizes sind, da sie schneller ausgeführt werden.quelle
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.
quelle
Zwei mögliche Antworten:
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.
Möglicherweise müssen Sie diese Anweisung in eine Transaktion einfügen.
quelle
explain (analyze,buffers,timing) ...
herauszufinden, welche Indizes Ihnen fehlen.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.
quelle
Der einfachste Weg, dies zu tun, besteht darin, alle Ihre Einschränkungen zu löschen und dann das Löschen durchzuführen.
quelle
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
SELECT
Innere 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 NULL
Höchstwahrscheinlich ist dies nicht erforderlich, aber ich habe es nicht gewagt, es zu versuchen.Andere Dinge zu beachten sind
quelle
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 ...
)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 habensome_other_table
!Ich hatte ein ähnliches Problem und benutzte es
auto_explain
mitauto_explain.log_nested_statements = true
, was ergab, dass dasdelete
tatsächlich seq_scans aufsome_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.
quelle