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?