Warum hat Postgres UPDATE 39 Stunden gedauert?


17

Ich habe eine Postgres-Tabelle mit ~ 2,1 Millionen Zeilen. Ich habe das folgende Update durchgeführt:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

Die Ausführung dieser Abfrage dauerte 39 Stunden. Ich verwende dies auf einem 4 (physischen) Kern i7 Q720 Laptop-Prozessor, viel RAM, sonst läuft die überwiegende Mehrheit der Zeit nichts. Keine Festplattenspeicherbeschränkungen. Die Tabelle wurde kürzlich gesaugt, analysiert und neu indiziert.

Während der gesamten Zeit, in der die Abfrage ausgeführt wurde, war die WITHCPU-Auslastung in der Regel niedrig und die Festplatte war zu 100% belegt. Die Festplatte war so stark ausgelastet, dass jede andere App deutlich langsamer lief als normal.

Die Leistungseinstellung des Laptops war Hochleistung (Windows 7 x 64).

Hier ist der EXPLAIN:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1schließt nur einige Zehntausende von Zeilen aus. Trotz dieser WHEREKlausel arbeite ich immer noch mit über 2 Millionen Zeilen.

Die Festplatte ist vollständig mit TrueCrypt 7.1a verschlüsselt. Dass verlangsamen Dinge ein wenig nach unten, aber nicht genug , um eine Abfrage zu veranlassen , zu nehmen , dass viele Stunden.

Die WITHAusführung des Teils dauert nur ca. 3 Minuten.

Das arrest_idFeld hatte keinen Index für Fremdschlüssel. Diese Tabelle enthält 8 Indizes und 2 Fremdschlüssel. Alle anderen Felder in der Abfrage werden indiziert.

Das arrest_idFeld hatte keine Einschränkungen außer NOT NULL.

Die Tabelle enthält insgesamt 32 Spalten.

arrest_idist vom Typ Zeichen variiert (20) . Mir ist klar, dass rank()ein numerischer Wert erzeugt wird, aber ich muss Zeichenvariation (20) verwenden, weil ich andere Zeilen habe, in citing_jurisdiction<>1denen nicht numerische Daten für dieses Feld verwendet werden.

Das arrest_idFeld war für alle Zeilen mit leer citing_jurisdiction=1.

Dies ist ein persönlicher High-End-Laptop (Stand vor 1 Jahr). Ich bin der einzige Benutzer. Es wurden keine anderen Abfragen oder Vorgänge ausgeführt. Das Sperren scheint unwahrscheinlich.

Es gibt keine Trigger in dieser Tabelle oder irgendwo anders in der Datenbank.

Andere Vorgänge in dieser Datenbank benötigen nie sonderlich viel Zeit. Bei richtiger Indizierung sind SELECTAbfragen normalerweise recht schnell.


Das Seq Scansind ein bisschen beängstigend ...
Rogerdpack

Antworten:


18

Ich hatte vor kurzem etwas Ähnliches mit einer Tabelle von 3,5 Millionen Zeilen passiert. Mein Update würde niemals fertig werden. Nach vielem Experimentieren und Frust habe ich endlich den Täter gefunden. Es stellte sich heraus, dass die Indizes für die Tabelle aktualisiert wurden.

Die Lösung bestand darin, alle Indizes in der zu aktualisierenden Tabelle zu löschen, bevor die Aktualisierungsanweisung ausgeführt wurde. Sobald ich das getan habe, ist das Update in wenigen Minuten abgeschlossen. Nach Abschluss des Updates habe ich die Indizes neu erstellt und war wieder im Geschäft. Dies wird Ihnen an dieser Stelle wahrscheinlich nicht weiterhelfen, aber es könnte jemand anders sein, der nach Antworten sucht.

Ich würde die Indizes auf dem Tisch belassen, von dem Sie die Daten abrufen. Dieser muss keine Indizes mehr aktualisieren und sollte bei der Suche nach den zu aktualisierenden Daten hilfreich sein. Es lief gut auf einem langsamen Laptop.


3
Ich schalte die beste Antwort auf Sie. Seitdem ich dies gepostet habe, bin ich auf andere Situationen gestoßen, in denen Indizes das Problem sind, auch wenn die zu aktualisierende Spalte bereits einen Wert und keinen Index (!) Hat. Es scheint, dass Postgres ein Problem damit hat, wie Indizes für andere Spalten verwaltet werden. Es gibt keinen Grund für diese anderen Indizes, die Abfragezeit einer Aktualisierung zu verkürzen, wenn die einzige Änderung an einer Tabelle darin besteht, eine nicht indizierte Spalte zu aktualisieren, und Sie den zugewiesenen Speicherplatz für keine Zeile dieser Spalte erhöhen.
Aren Cambre

1
Vielen Dank! Hoffe es hilft anderen. Es hätte mir stundenlange Kopfschmerzen für etwas scheinbar sehr Einfaches erspart.
JC Avena

5
@ArenCambre - es gibt einen Grund: PostgreSQL kopiert die gesamte Zeile an einen anderen Ort und markiert die alte Version als gelöscht. So implementiert PostgreSQL MVCC (Multi-Version Concurrency Control).
Piotr Findeisen

Meine Frage ist ... warum ist es der Täter? Siehe auch stackoverflow.com/a/35660593/32453
Rogerdpack

15

Ihr größtes Problem besteht darin, große Mengen schreib- und suchintensiver Arbeit auf einer Laptop-Festplatte zu verrichten. Das wird nie schnell, egal was Sie tun, besonders wenn es sich um ein langsameres Laufwerk mit 5400 U / min handelt, das in vielen Laptops geliefert wird.

TrueCrypt verlangsamt die Schreibvorgänge mehr als "ein bisschen". Lesevorgänge sind relativ schnell, Schreibvorgänge lassen RAID 5 jedoch schnell aussehen. Das Ausführen einer Datenbank auf einem TrueCrypt-Volume ist eine Qual für Schreibvorgänge, insbesondere für zufällige Schreibvorgänge.

In diesem Fall würden Sie wahrscheinlich Ihre Zeit damit verschwenden, die Abfrage zu optimieren. Sie schreiben sowieso die meisten Zeilen neu und es wird langsam mit Ihrer schrecklichen Schreibsituation. Was ich empfehlen würde, ist:

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

Ich vermute, dass dies schneller geht, als nur die Einschränkungen zu löschen und neu zu erstellen, da ein UPDATE ziemlich zufällige Schreibmuster enthält, die Ihren Speicherplatz zerstören. Zwei Masseneinfügungen, eine in eine nicht protokollierte Tabelle und eine in eine WAL-protokollierte Tabelle ohne Einschränkungen, sind wahrscheinlich schneller.

Wenn Sie über absolut aktuelle Sicherungen verfügen und es Ihnen nichts ausmacht, Ihre Datenbank aus Sicherungen wiederherzustellen, können Sie PostgreSQL auch mit dem fsync=offParameter und full_page_writes=off vorübergehend für diese Massenoperation neu starten . Jedes unerwartete Problem wie ein Stromausfall oder ein Betriebssystemabsturz lässt Ihre Datenbank währenddessen nicht wiederherstellbar fsync=off.

Das POSTGreSQL-Äquivalent zu "keine Protokollierung" besteht darin, nicht protokollierte Tabellen zu verwenden. Diese nicht protokollierten Tabellen werden abgeschnitten, wenn die Datenbank unsauber heruntergefahren wird, während sie fehlerhaft sind. Wenn Sie nicht protokollierte Tabellen verwenden, halbieren Sie mindestens Ihre Schreiblast und reduzieren die Anzahl der Suchvorgänge, sodass diese VIEL schneller ausgeführt werden können.

Wie in Oracle kann es eine gute Idee sein, einen Index zu löschen und nach einem großen Batch-Update neu zu erstellen. Der Planer von PostgreSQL kann nicht feststellen, dass ein großes Update stattfindet. Halten Sie die Indexaktualisierungen an und erstellen Sie den Index am Ende neu. Selbst wenn es möglich wäre, wäre es sehr schwierig, herauszufinden, an welchem ​​Punkt sich dies lohnt, besonders im Voraus.


Diese Antwort ist genau richtig für die große Anzahl von Schreibvorgängen und die schreckliche Perfektion der Verschlüsselung sowie das langsame Laptop-Laufwerk. Ich würde auch beachten , dass das Vorhandensein von 8 Indizes viele zusätzliche schreibt und Niederlagen Anwendbarkeit produziert HOT in Blockreihe Updates, so Löschen von Indizes und mit einer geringeren Füllfaktor auf dem Tisch verhindern kann eine Tonne Reihe Migration
dbenhur

1
Guter Ruf, die Chancen von HOTs mit einem Füllfaktor zu erhöhen - obwohl ich mir nicht sicher bin, ob TrueCrypt das Erzwingen von Lese- und Schreibzyklen von Blöcken in großen Blöcken hilft. Die Zeilenmigration kann sogar noch schneller sein, da das Anwachsen der Tabelle zumindest lineare Schreibblöcke erfordert.
Craig Ringer

2,5 Jahre später mache ich etwas Ähnliches, aber auf einem größeren Tisch. Ist es eine gute Idee, alle Indizes zu löschen, auch wenn die einzelne Spalte, die ich aktualisiere, nicht indiziert ist?
Aren Cambre

1
@ArenCambre In diesem Fall ... na ja, es ist kompliziert. Wenn die meisten Ihrer Aktualisierungen in Frage kommen, sollten HOTSie die Indizes an Ort und Stelle belassen. Wenn nicht, möchten Sie wahrscheinlich löschen und neu erstellen. Die Spalte ist nicht indiziert, aber um ein HOT-Update durchführen zu können, muss auf derselben Seite auch freier Speicherplatz vorhanden sein. Dies hängt also ein wenig davon ab, wie viel Totraum in der Tabelle vorhanden ist. Wenn es hauptsächlich schreibend ist, würde ich sagen, dass alle Indizes gelöscht werden. Wenn es Lose aktualisiert hat, hat es möglicherweise Löcher und Sie sind möglicherweise in Ordnung. Tools wie pageinspectund pg_freespacemapkönnen dabei helfen, dies festzustellen.
Craig Ringer

Vielen Dank. In diesem Fall handelt es sich um eine Boolesche Spalte, die bereits einen Eintrag in jede Zeile hatte. Ich habe den Eintrag in einigen Zeilen geändert. Ich habe gerade bestätigt: Das Update dauerte nur 2 Stunden, nachdem alle Indizes gelöscht wurden. Vorher musste ich das Update nach 18 Stunden beenden, weil es einfach zu lange dauerte. Dies trotz der Tatsache, dass die Spalte, die definitiv aktualisiert wurde, nicht indiziert wurde.
Aren Cambre

2

Jemand wird eine bessere Antwort für Postgres geben, aber hier sind einige Beobachtungen aus einer Oracle-Perspektive, die zutreffen könnten (und die Kommentare sind zu lang für das Kommentarfeld).

Meine erste Sorge wäre, zu versuchen, 2 Millionen Zeilen in einer Transaktion zu aktualisieren. In Oracle schreiben Sie ein Vorabbild jedes zu aktualisierenden Blocks, sodass in einer anderen Sitzung immer noch ein konsistenter Lesevorgang stattfindet, ohne die geänderten Blöcke zu lesen, und Sie haben die Möglichkeit, ein Rollback durchzuführen. Das ist ein langer Rollback. Sie sind normalerweise besser dran, die Transaktionen in kleinen Stücken durchzuführen. Sagen Sie 1.000 Datensätze gleichzeitig.

Wenn Sie Indizes für die Tabelle haben und die Tabelle während der Wartung als außer Betrieb betrachtet wird, ist es häufig besser, die Indizes vor einer großen Operation zu entfernen und sie anschließend erneut zu erstellen. Billiger ist es dann, ständig zu versuchen, die Indizes mit jedem aktualisierten Datensatz zu pflegen.

Oracle lässt Hinweise auf Anweisungen ohne Protokollierung zu, um das Journalling zu stoppen. Dadurch werden die Anweisungen erheblich beschleunigt, aber Ihre Datenbank befindet sich in einer "nicht behebbaren" Situation. Sie möchten also vorher und unmittelbar danach erneut sichern. Ich weiß nicht, ob Postgres ähnliche Optionen hat.


PostgreSQL hat keine Probleme mit einem langen Rollback, existiert nicht. ROLBACK ist in PostgreSQL sehr schnell, egal wie groß Ihre Transaktion ist. Oracle! = PostgreSQL
Frank Heikens

@FrankHeikens Danke, das ist interessant. Ich muss nachlesen, wie das Journaling auf Postgres funktioniert. Damit das gesamte Transaktionskonzept funktioniert, müssen während einer Transaktion zwei verschiedene Versionen der Daten gepflegt werden, das Vorher-Bild und das Nachher-Bild. Dies ist der Mechanismus, auf den ich mich beziehe. Auf die eine oder andere Weise würde ich vermuten, dass es einen Schwellenwert gibt, ab dem die Ressourcen zur Aufrechterhaltung der Transaktion zu teuer sind.
Glenn

2
@Glenn Postgres hält die Versionen einer Zeile in der Tabelle selbst - siehe hier für eine Erklärung. Der Kompromiss besteht darin, dass "tote" Tupel herumhängen, die asynchron mit dem "Vakuum" in Postgres aufgeräumt werden (Oracle benötigt kein Vakuum, weil es nie "tote" Zeilen in der Tabelle selbst hat)
Jack Douglas

Gern geschehen und verspätet: Willkommen auf der Seite :-)
Jack Douglas

@Glenn Das kanonische Dokument für die Parallelitätskontrolle der Zeilenversion von PostgreSQL lautet postgresql.org/docs/current/static/mvcc-intro.html und ist einen Besuch wert. Siehe auch wiki.postgresql.org/wiki/MVCC . Beachten Sie, dass MVCC mit toten Zeilen und VACUUMnur die halbe Antwort ist; PostgreSQL verwendet auch ein sogenanntes "Write Ahead Log" ( quasi
Craig Ringer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.