Optimieren der Leistung von Massenupdates in PostgreSQL

37

Verwendung von PG 9.1 unter Ubuntu 12.04.

Derzeit dauert es bis zu 24 Stunden, bis wir eine große Anzahl von UPDATE-Anweisungen in einer Datenbank ausgeführt haben, die folgende Form haben:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(Wir überschreiben nur Felder von Objekten, die durch ID gekennzeichnet sind.) Die Werte stammen aus einer externen Datenquelle (nicht bereits in der Datenbank in einer Tabelle).

Die Tabellen haben jeweils eine Handvoll Indizes und keine Fremdschlüsseleinschränkungen. Bis zum Ende wird kein COMMIT durchgeführt.

Es dauert 2 Stunden, um eine pg_dumpder gesamten DB zu importieren . Dies scheint eine Grundlinie zu sein, auf die wir angemessen zielen sollten.

Abgesehen von der Erstellung eines benutzerdefinierten Programms, das einen Datensatz für den erneuten Import von PostgreSQL auf irgendeine Weise rekonstruiert, können wir noch etwas tun, um die UPDATE-Massenleistung näher an die des Imports heranzuführen? (Dies ist ein Bereich, der unserer Meinung nach mit logarithmisch strukturierten Zusammenführungsbäumen gut zurechtkommt, aber wir fragen uns, ob wir in PostgreSQL etwas tun können.)

Einige Ideen:

  • Alle Nicht-ID-Indizes löschen und danach neu erstellen?
  • Steigern Sie checkpoint_segments, aber hilft dies tatsächlich, den Durchsatz langfristig aufrechtzuerhalten?
  • Verwenden Sie die hier genannten Techniken ? (Neue Daten als Tabelle laden, dann alte Daten "zusammenführen", wobei ID in neuen Daten nicht gefunden wird.)

Grundsätzlich gibt es eine Menge Dinge zu versuchen und wir sind uns nicht sicher, welche am effektivsten sind oder ob wir andere Dinge übersehen. Wir werden die nächsten Tage damit verbringen, zu experimentieren, aber wir dachten, wir würden auch hier nachfragen.

Die Tabelle ist zwar gleichzeitig geladen, aber schreibgeschützt.

Yang
quelle
In Ihrer Frage fehlen wichtige Informationen: Ihre Version von Postgres? Woher kommen die Werte? Klingt wie eine Datei außerhalb der Datenbank, aber bitte klären. Ist die Zieltabelle gleichzeitig geladen? Wenn ja, was genau? Oder können Sie es sich leisten, zu fallen und neu zu erstellen? Keine Fremdschlüssel, ok - aber gibt es andere abhängige Objekte wie Ansichten? Bitte bearbeiten Sie Ihre Frage mit den fehlenden Informationen. Drücke es nicht in einen Kommentar.
Erwin Brandstetter
@ ErwinBrandstetter Danke, aktualisiert meine Frage.
Yang
Ich nehme an, Sie haben überprüft, explain analyzeob ein Index für die Suche verwendet wird.
Rogerdpack

Antworten:

45

Annahmen

Da Informationen im Q fehlen, gehe ich davon aus:

  • Ihre Daten stammen aus einer Datei auf dem Datenbankserver.
  • Die Daten werden wie die COPYAusgabe formatiert , und zwar mit einer eindeutigen Angabe idpro Zeile, die mit der Zieltabelle übereinstimmt.
    Wenn nicht, formatieren Sie es zuerst richtig oder verwenden Sie die COPYOptionen, um das Format zu verarbeiten.
  • Sie aktualisieren jede einzelne Zeile in der Zieltabelle oder die meisten davon.
  • Sie können es sich leisten, die Zieltabelle zu löschen und neu zu erstellen.
    Das bedeutet keinen gleichzeitigen Zugriff. Andernfalls betrachten Sie diese verwandte Antwort:
  • Es gibt überhaupt keine abhängigen Objekte außer Indizes.

Lösung

Ich schlage vor, Sie gehen mit einem ähnlichen Ansatz vor, wie unter dem Link von Ihrem dritten Aufzählungszeichen beschrieben . Mit großen Optimierungen.

Es gibt einen einfacheren und schnelleren Weg, um die temporäre Tabelle zu erstellen:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

Ein einzelner Big UPDATEaus einer temporären Tabelle in der Datenbank ist um mehrere Größenordnungen schneller als einzelne Aktualisierungen von außerhalb der Datenbank.

In PostgreSQLs MVCC Modell , ein UPDATEMittel , eine neue Zeile Version und markieren Sie die alten zu erstellen , wie gelöscht. Das ist ungefähr so ​​teuer wie eine INSERTund eine DELETEKombination. Außerdem bleiben viele tote Tupel übrig. Da Sie ohnehin die gesamte Tabelle aktualisieren, ist es insgesamt schneller, eine neue Tabelle zu erstellen und die alte zu löschen.

Wenn Sie genug RAM zur Verfügung haben, stellen Sie temp_buffers(nur für diese Sitzung!) Hoch genug ein, um die temporäre Tabelle im RAM zu halten - bevor Sie etwas anderes tun.

Führen Sie einen Test mit einer kleinen Stichprobe durch und verwenden Sie die db-Objektgrößenfunktionen, um eine Schätzung des RAM-Bedarfs zu erhalten :

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

Komplettes Drehbuch

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

Gleichzeitige Ladung

Gleichzeitige Operationen an der Tabelle (die ich in den Annahmen am Anfang ausgeschlossen habe) warten, sobald die Tabelle gegen Ende gesperrt ist, und schlagen fehl, sobald die Transaktion festgeschrieben ist, da der Tabellenname sofort in seine OID aufgelöst wird, aber Die neue Tabelle hat eine andere OID. Die Tabelle bleibt konsistent, aber gleichzeitige Vorgänge können eine Ausnahme erhalten und müssen wiederholt werden. Details in dieser verwandten Antwort:

UPDATE Route

Wenn Sie die UPDATERoute gehen (müssen) , löschen Sie einen Index, der während des Updates nicht benötigt wird, und erstellen Sie ihn anschließend neu. Es ist viel billiger, einen Index in einem Stück zu erstellen, als ihn für jede einzelne Zeile zu aktualisieren. Dies kann auch HOT-Updates ermöglichen .

Ich habe UPDATEin dieser eng verwandten Antwort zu SO ein ähnliches Verfahren beschrieben .

 

Erwin Brandstetter
quelle
1
Ich aktualisiere gerade 20% der Zeilen in der Zieltabelle - nicht alle, aber ein ausreichend großer Teil, dass eine Zusammenführung wahrscheinlich besser ist als eine zufällige Aktualisierung.
Yang
1
@AryehLeibTaurog: Das sollte nicht passieren, da DROP TABLEnimmt ein Access Exclusive Lock. In beiden Fällen habe ich die Voraussetzung bereits oben in meiner Antwort aufgeführt: You can afford to drop and recreate the target table.Es kann hilfreich sein, die Tabelle zu Beginn der Transaktion zu sperren. Ich schlage vor, Sie beginnen eine neue Frage mit allen relevanten Details Ihrer Situation, damit wir dem auf den Grund gehen können.
Erwin Brandstetter
1
@ErwinBrandstetter Interessant. Es scheint von der Serverversion abzuhängen. Ich habe den Fehler auf 8.4 und 9.1 mit dem Adapter psycopg2 und dem Client psql reproduziert . Bei 9.3 liegt kein Fehler vor. Siehe meine Kommentare im ersten Skript. Ich bin mir nicht sicher, ob es eine Frage zum Posten gibt, aber es kann sich lohnen, einige Informationen in einer der postgresql-Listen einzuholen.
Aryeh Leib Taurog
1
Ich habe eine einfache Hilfsklasse in Python geschrieben, um den Prozess zu automatisieren.
Aryeh Leib Taurog
3
Sehr nützliche Antwort. Als geringfügige Abweichung kann man die temporäre Tabelle nur mit den zu aktualisierenden Spalten und Referenzspalten erstellen, die zu aktualisierenden Spalten aus der Originaltabelle löschen und dann die Tabellen mit zusammenführen CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;, LEFT JOINum Zeilen beizubehalten, für die es keine Aktualisierung gibt. Natürlich NATURALkann das zu jedem gültigen USING()oder geändert werden ON.
Skippy le Grand Gourou
2

Wenn die Daten in einer strukturierten Datei verfügbar gemacht werden können, können Sie sie mit einem Wrapper für fremde Daten lesen und eine Zusammenführung für die Zieltabelle durchführen.

David Aldridge
quelle
3
Was meinen Sie konkret mit "Zusammenführen auf der Zieltabelle"? Warum ist es besser, FDW zu verwenden, als in eine temporäre Tabelle zu kopieren (wie im dritten Punkt der ursprünglichen Frage vorgeschlagen)?
Yang
"Merge" wie in der MERGE-SQL-Anweisung. Mit FDW können Sie dies ohne den zusätzlichen Schritt des Kopierens der Daten in eine temporäre Tabelle tun. Ich gehe davon aus, dass Sie nicht den gesamten Datensatz ersetzen und dass sich in der Datei eine bestimmte Datenmenge befindet, die keine Änderung gegenüber dem aktuellen Datensatz darstellt - wenn sich eine erhebliche Menge geändert hat, dann eine vollständige Ein Austausch des Tisches kann sich lohnen.
David Aldridge
1
@DavidAldridge: Obwohl im SQL: 2003-Standard definiert, MERGEist es (noch) nicht in PostgreSQL implementiert . Die Implementierungen in anderen RDBMS variieren erheblich. Beachten Sie die Tag-Informationen für MERGEund UPSERT.
Erwin Brandstetter
@ErwinBrandstetter [glurk] Oh ja ganz so. Nun, Merge ist das i-Tüpfelchen, nehme ich an. Der Zugriff auf die Daten ohne den Schritt "Import in temporäre Tabelle" ist der Kern der FDW-Technik.
David Aldridge