PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie führe ich große nicht blockierende Updates in PostgreSQL durch?

Spalte / Zeile

... Ich brauche nicht, dass die Transaktionsintegrität während des gesamten Vorgangs beibehalten wird, weil ich weiß, dass die Spalte, die ich ändere, während der Aktualisierung nicht beschrieben oder gelesen wird.

Beliebiges UPDATE im MVCC-Modell von PostgreSQL schreibt eine neue Version von der ganzen Zeile . Wenn sich gleichzeitige Transaktionen beliebig ändern Spalte derselben Zeile, treten zeitaufwändige Parallelitätsprobleme auf. Details im Handbuch. Die gleiche Spalte kennen wird nicht von gleichzeitigen Transaktionen berührt vermeidet einige mögliche Komplikationen, aber nicht andere.

Index

Um nicht zu einer offtopischen Diskussion abgelenkt zu werden, nehmen wir an, dass alle Statuswerte für die 35 Millionen Spalten derzeit auf denselben Wert (nicht null) gesetzt sind, wodurch ein Index unbrauchbar wird.

Beim Aktualisieren der gesamten Tabelle (oder große Teile davon) Postgres verwendet niemals einen Index . Ein sequenzielles Scannen ist schneller, wenn alle oder die meisten Zeilen gelesen werden müssen. Im Gegenteil:Indexpflege bedeutet zusätzliche Kosten für das UPDATE .

Leistung

Nehmen wir zum Beispiel an, ich habe eine Tabelle mit dem Namen "Bestellungen" mit 35 Millionen Zeilen und möchte dies tun:

UPDATE orders SET status = null;

Soweit ich weiß, streben Sie eine allgemeinere Lösung an (siehe unten). Aber um die eigentliche Frage anzusprechen gefragt:Dies kann in einer Angelegenheit von Millisekunden erledigt werden , unabhängig von der Tabellengröße:

ALTER TABLE orders DROP column status
                 , ADD  column status text;

Das Handbuch (bis Postgres 10):

Wenn eine Spalte mit ADD COLUMN hinzugefügt wird werden alle vorhandenen Zeilen in der Tabelle mit dem Standardwert der Spalte initialisiert (NULL wenn kein DEFAULT Klausel angegeben ist). Wenn kein DEFAULT vorhanden ist Klausel, dies ist lediglich eine Metadatenänderung [...]

Das Handbuch (seit Postgres 11):

Wenn eine Spalte mit ADD COLUMN hinzugefügt wird und ein nichtflüchtiges DEFAULT angegeben ist, wird der Standard zum Zeitpunkt der Anweisung ausgewertet und das Ergebnis in den Metadaten der Tabelle gespeichert. Dieser Wert wird für die Spalte für alle vorhandenen Zeilen verwendet. Wenn kein DEFAULT angegeben ist, wird NULL verwendet. In keinem Fall ist ein Umschreiben der Tabelle erforderlich.

Hinzufügen einer Spalte mit einem flüchtigen DEFAULT Wenn Sie den Typ einer vorhandenen Spalte ändern, müssen die gesamte Tabelle und ihre Indizes neu geschrieben werden. [...]

Und:

Die DROP COLUMN Form entfernt die Spalte nicht physisch, sondern macht sie für SQL-Operationen einfach unsichtbar. Nachfolgende Einfüge- und Aktualisierungsvorgänge in der Tabelle speichern einen Nullwert für die Spalte. Das Löschen einer Spalte geht also schnell, verringert jedoch nicht sofort die Größe Ihrer Tabelle auf der Festplatte, da der von der gelöschten Spalte belegte Speicherplatz nicht wiedergewonnen wird. Der Speicherplatz wird im Laufe der Zeit zurückgewonnen, wenn vorhandene Zeilen aktualisiert werden.

Stellen Sie sicher, dass Sie keine Objekte haben, die von der Spalte abhängen (Fremdschlüsseleinschränkungen, Indizes, Ansichten, ...). Sie müssten diese löschen / neu erstellen. Abgesehen davon, winzige Operationen auf der Systemkatalogtabelle pg_attribute mach den Job. Erfordert eine exklusive Sperre auf dem Tisch, was bei hoher gleichzeitiger Belastung ein Problem darstellen kann. (Wie Buurman in seinem Kommentar betont.) Abgesehen davon ist die Operation eine Sache von Millisekunden.

Wenn Sie einen Spaltenstandard beibehalten möchten, fügen Sie ihn in einem separaten Befehl wieder hinzu . Wenn Sie dies im selben Befehl tun, wird es sofort auf alle Zeilen angewendet. Siehe:

  • Neue Spalte ohne Tabellensperre hinzufügen?

Um die Standardeinstellung tatsächlich anzuwenden, sollten Sie dies stapelweise tun:

  • Optimiert PostgreSQL das Hinzufügen von Spalten mit Nicht-NULL-DEFAULTs?

Allgemeine Lösung

dblink wurde in einer anderen Antwort erwähnt. Es ermöglicht den Zugriff auf "entfernte" Postgres-Datenbanken in impliziten separaten Verbindungen. Die "entfernte" Datenbank kann die aktuelle sein, wodurch "autonome Transaktionen" erreicht werden :was die Funktion in die "remote" db schreibt wird festgeschrieben und kann nicht rückgängig gemacht werden.

Dies ermöglicht es, eine einzelne Funktion auszuführen, die eine große Tabelle in kleineren Teilen aktualisiert, und jeder Teil wird separat festgeschrieben. Vermeidet den Aufbau von Transaktionsaufwand für eine sehr große Anzahl von Zeilen und, was noch wichtiger ist, gibt Sperren nach jedem Teil frei. Dadurch können gleichzeitige Vorgänge ohne große Verzögerung fortgesetzt werden, und Deadlocks werden weniger wahrscheinlich.

Wenn Sie keinen gleichzeitigen Zugriff haben, ist dies kaum sinnvoll - außer um ROLLBACK zu vermeiden nach einer Ausnahme. Beachten Sie auch SAVEPOINT für diesen Fall.

Haftungsausschluss

Zunächst einmal sind viele kleine Transaktionen tatsächlich teurer. Dies macht nur bei großen Tischen Sinn . Der optimale Punkt hängt von vielen Faktoren ab.

Wenn Sie sich nicht sicher sind, was Sie tun:eine einzelne Transaktion ist die sichere Methode . Damit das richtig funktioniert, müssen nebenläufige Operationen auf dem Tisch mitspielen. Zum Beispiel:gleichzeitige Schreibvorgänge kann eine Zeile in eine Partition verschieben, die angeblich bereits verarbeitet wurde. Oder gleichzeitige Lesevorgänge können inkonsistente Zwischenzustände erkennen. Sie wurden gewarnt.

Schritt-für-Schritt-Anleitung

Das Zusatzmodul dblink muss zuerst installiert werden:

  • Wie wird dblink in PostgreSQL verwendet (installiert)?

Das Einrichten der Verbindung mit dblink hängt stark von der Einrichtung Ihres DB-Clusters und den vorhandenen Sicherheitsrichtlinien ab. Es kann schwierig sein. Zugehörige spätere Antwort mit mehr Wie man sich mit dblink verbindet :

  • Permanente Einfügungen in eine UDF, auch wenn die Funktion abgebrochen wird

Erstellen Sie einen FOREIGN SERVER und eine USER MAPPING wie dort angewiesen, um die Verbindung zu vereinfachen und zu rationalisieren (es sei denn, Sie haben bereits eine).
Angenommen ein serial PRIMARY KEY mit oder ohne Lücken.

CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(order_id), max(order_id) FROM orders;
                                        -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 100) + 1;  -- rounded, possibly a bit too small
                                        -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('myserver');  -- your foreign server as instructed above

   FOR i IN 0..200 LOOP                 -- 200 >> 100 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE public.orders
         SET    status = 'foo'
         WHERE  order_id >= $$ || _cur || $$
         AND    order_id <  $$ || _cur + _step || $$
         AND    status IS DISTINCT FROM 'foo'$$);  -- avoid empty update

      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

Aufruf:

SELECT f_update_in_steps();

Sie können jeden Teil nach Ihren Bedürfnissen parametrisieren:den Tabellennamen, den Spaltennamen, den Wert, ... stellen Sie nur sicher, dass Sie die Bezeichner bereinigen, um eine SQL-Injektion zu vermeiden:

  • Tabellenname als PostgreSQL-Funktionsparameter

Vermeiden Sie leere UPDATEs:

  • Wie kann ich (oder kann ich) DISTINCT für mehrere Spalten auswählen?