Wir haben eine 2,2 GB-Tabelle in Postgres mit 7.801.611 Zeilen. Wir fügen eine uuid / guid-Spalte hinzu, und ich frage mich, wie diese Spalte am besten ausgefüllt werden kann (da wir ihr eine NOT NULL
Einschränkung hinzufügen möchten ).
Wenn ich Postgres richtig verstehe, ist ein Update technisch ein Löschen und Einfügen, so dass im Grunde die gesamte 2,2-GB-Tabelle neu erstellt wird. Wir haben auch einen Sklaven am Laufen, damit dieser nicht zurückbleibt.
Gibt es einen besseren Weg, als ein Skript zu schreiben, das es mit der Zeit langsam füllt?
postgresql
storage
ddl
Collin Peters
quelle
quelle
ALTER TABLE .. ADD COLUMN ...
oder soll dieser Teil auch beantwortet werden?Antworten:
Es kommt sehr auf die Details Ihrer Anforderungen an.
Wenn Sie über ausreichend freien Speicherplatz (mindestens 110% von
pg_size_pretty((pg_total_relation_size(tbl))
) auf der Festplatte verfügen und sich eine Freigabesperre für einige Zeit und eine exklusive Sperre für eine sehr kurze Zeit leisten können , erstellen Sie eine neue Tabelle mit deruuid
Spalte usingCREATE TABLE AS
. Warum?Der folgende Code verwendet eine Funktion aus dem
uuid-oss
Zusatzmodul .Sperren Sie die Tabelle gegen gleichzeitige Änderungen im
SHARE
Modus (gleichzeitige Lesevorgänge sind weiterhin zulässig). Versuche, in die Tabelle zu schreiben, warten und schlagen schließlich fehl. Siehe unten.Kopieren Sie die gesamte Tabelle, während Sie die neue Spalte im Handumdrehen füllen, und ordnen Sie dabei möglicherweise die Zeilen günstig an.
Wenn Sie Zeilen neu anordnen möchten, stellen Sie sicher, dass Sie
work_mem
so hoch wie möglich einstellen (nur für Ihre Sitzung, nicht global).Fügen Sie dann Einschränkungen, Fremdschlüssel, Indizes, Trigger usw. zur neuen Tabelle hinzu. Wenn Sie große Teile einer Tabelle aktualisieren, ist es viel schneller, Indizes von Grund auf neu zu erstellen, als Zeilen iterativ hinzuzufügen.
Wenn die neue Tabelle fertig ist, löschen Sie die alte und benennen Sie die neue um, um sie als Drop-In-Ersatz zu verwenden. Nur dieser letzte Schritt erhält eine exklusive Sperre für den alten Tisch für den Rest der Transaktion - die jetzt sehr kurz sein sollte.
Es erfordert auch, dass Sie jedes Objekt abhängig vom Tabellentyp (Ansichten, Funktionen, die den Tabellentyp in der Signatur verwenden, ...) löschen und anschließend neu erstellen.
Machen Sie alles in einer Transaktion, um unvollständige Status zu vermeiden.
Dies sollte am schnellsten sein. Jede andere Methode zum Aktualisieren an Ort und Stelle muss auch die gesamte Tabelle neu schreiben, nur auf eine teurere Art und Weise. Sie würden diesen Weg nur gehen, wenn Sie nicht genügend freien Speicherplatz auf der Festplatte haben oder es sich nicht leisten können, die gesamte Tabelle zu sperren oder Fehler für gleichzeitige Schreibversuche zu generieren.
Was passiert mit gleichzeitigen Schreibvorgängen?
Andere Transaktionen (in anderen Sitzungen), die versuchen, nach dem Aufheben der Sperre in derselben Tabelle zu
INSERT
/UPDATE
/ zu landen, warten, bis die Sperre aufgehoben wird oder eine Zeitüberschreitung eintritt, je nachdem, was zuerst eintritt. Sie schlagen in beiden Fällen fehl , da die Tabelle, in die sie schreiben wollten, unter ihnen gelöscht wurde.DELETE
SHARE
Die neue Tabelle hat eine neue Tabellen-OID, aber die gleichzeitige Transaktion hat den Tabellennamen bereits in die OID der vorherigen Tabelle aufgelöst . Wenn die Sperre endlich aufgehoben wird, versuchen sie, die Tabelle selbst zu sperren, bevor sie darauf schreibt, und stellen fest, dass sie weg ist. Postgres wird antworten:
Wo
123456
ist die OID der alten Tabelle. Sie müssen diese Ausnahme abfangen und Abfragen in Ihrem App-Code wiederholen, um sie zu vermeiden.Wenn Sie sich das nicht leisten können, müssen Sie Ihren Originaltisch behalten .
Zwei Alternativen, um die vorhandene Tabelle beizubehalten
Aktualisieren Sie vor dem Hinzufügen der
NOT NULL
Einschränkung an Ort und Stelle (möglicherweise wird die Aktualisierung für kleine Segmente gleichzeitig ausgeführt) . Das Hinzufügen einer neuen Spalte mit NULL-Werten und ohneNOT NULL
Einschränkung ist kostengünstig.Seit Postgres 9.2 können Sie auch eine
CHECK
EinschränkungNOT VALID
erstellen mit :Auf diese Weise können Sie Zeilen peu à peu aktualisieren - in mehreren separaten Transaktionen . Dadurch wird vermieden, dass Zeilensperren zu lange beibehalten werden, und tote Zeilen können wiederverwendet werden. (Sie müssen
VACUUM
manuell ausgeführt werden, wenn zwischen den einzelnen Schritten nicht genügend Zeit liegt , damit das automatische Absaugen einsetzt.) Fügen Sie schließlich dieNOT NULL
Einschränkung hinzu und entfernen Sie dieNOT VALID CHECK
Einschränkung:Verwandte Antwort, die
NOT VALID
ausführlicher bespricht:Bereiten Sie den neuen Status in einer temporären Tabelle vor ,
TRUNCATE
und füllen Sie das Original aus der temporären Tabelle nach. Alles in einer Transaktion . Bevor Sie die neue Tabelle vorbereiten, müssen Sie noch eineSHARE
Sperre aktivieren, um zu verhindern, dass gleichzeitige Schreibvorgänge verloren gehen.Details in dieser verwandten Antwort auf SO:
quelle
LOCK
bis und ohne das ausführenDROP
. Ich konnte nur wilde und nutzlose Vermutungen anstellen. Bezüglich 2. beachten Sie bitte den Nachtrag zu meiner Antwort.Ich habe keine "beste" Antwort, aber ich habe eine "am wenigsten schlechte" Antwort, mit der Sie die Dinge einigermaßen schnell erledigen können.
Meine Tabelle hatte 2-MM-Zeilen und die Aktualisierungsleistung war fehlerhaft, als ich versuchte, eine sekundäre Zeitstempelspalte hinzuzufügen, die standardmäßig der ersten entspricht.
Nachdem es 40 Minuten lang hängen geblieben war, versuchte ich es mit einer kleinen Menge, um eine Vorstellung davon zu bekommen, wie lange dies dauern könnte - die Prognose lag bei ungefähr 8 Stunden.
Die akzeptierte Antwort ist definitiv besser - aber diese Tabelle wird in meiner Datenbank häufig verwendet. Es gibt ein paar Dutzend Tische, die darauf FKEY; Ich wollte vermeiden, FOREIGN KEYS an so vielen Tabellen zu wechseln. Und dann gibt es Ansichten.
Ein bisschen nach Dokumenten, Fallstudien und StackOverflow suchen, und ich hatte das "A-Ha!" Moment. Der Drain lag nicht auf dem Kern-UPDATE, sondern auf allen INDEX-Operationen. Meine Tabelle enthielt 12 Indizes - einige für eindeutige Einschränkungen, einige für die Beschleunigung des Abfrageplaners und einige für die Volltextsuche.
Jede Zeile, die AKTUALISIERT wurde, arbeitete nicht nur an einem DELETE / INSERT, sondern auch an dem Aufwand, die einzelnen Indizes zu ändern und Einschränkungen zu überprüfen.
Meine Lösung bestand darin, jeden Index und jede Einschränkung zu löschen, die Tabelle zu aktualisieren und dann alle Indizes / Einschränkungen wieder hinzuzufügen.
Es dauerte ungefähr 3 Minuten, um eine SQL-Transaktion zu schreiben, die Folgendes ausführte:
Die Ausführung des Skripts dauerte 7 Minuten.
Die akzeptierte Antwort ist definitiv besser und richtiger ... und macht Ausfallzeiten praktisch überflüssig. In meinem Fall hätte die Verwendung dieser Lösung erheblich mehr "Entwicklerarbeit" in Anspruch genommen, und wir hatten ein 30-minütiges Zeitfenster für geplante Ausfallzeiten, in dem dies möglich war. Unsere Lösung hat sich in 10 Minuten damit befasst.
quelle