Atomisches Ersetzen von Tabellendaten in PostgreSQL

14

Ich möchte den gesamten Inhalt einer Tabelle ersetzen, ohne dabei eingehende SELECTAnweisungen zu beeinflussen .

Der Anwendungsfall ist eine Tabelle, in der Postfachinformationen gespeichert werden, die regelmäßig extrahiert werden, und die in einer PostgreSQL-Tabelle gespeichert werden muss. Es gibt viele Clients, die eine Anwendung verwenden, die ständig dieselbe Tabelle abfragt.

Normalerweise würde ich sowas machen (Pseudocode eingehend) ...

BEGIN TRANSACTION
TRUNCATE TABLE
INSERT INTO
COMMIT

Leider kann die Tabelle während dieses Vorgangs nicht gelesen werden. Aufgrund der Zeit, die INSERT INTOfür die Fertigstellung benötigt wird. Der Tisch ist gesperrt.

In MySQL hätte ich ihren atomaren RENAME TABLEBefehl verwendet, um diese Probleme zu vermeiden ...

CREATE TABLE table_new LIKE table; 
INSERT INTO table_new;
RENAME TABLE table TO table_old, table_new TO table; *atomic operation*
DROP TABLE table_old;

Wie könnte ich das in PostgreSQL erreichen?

In dieser Frage können Sie davon ausgehen, dass ich keine Fremdschlüssel verwende.

Clarkey
quelle
Warum kann die Tabelle Ihrer Meinung nach beim Einfügen von Zeilen nicht gelesen werden? "Tabelle abschneiden" wirkt sich sofort auf alle Sitzungen aus. Einfügungen (wenn sie in einer Transaktion ausgeführt werden, die sie alle umschließt, wie Ihr Pseudocode vorschlägt) sind jedoch für andere Sitzungen erst dann sichtbar, wenn Sie ein Commit durchführen. Andere Sitzungen können aus der Tabelle auswählen und sehen eine leere Tabelle, bis Sie ein Commit durchführen.
Zgguy
1
@zgguy Der TRUNCATEBefehl erhält eine AccessExclusive-Sperre für die Tabelle, sodass niemand sonst in der Lage ist, aus der Tabelle zu lesen, bis diese Transaktion festgeschrieben oder zurückgesetzt wird.
Josh Kupershmidt
2
Wenn Sie deletestattdessen verwenden truncate, wird es langsamer, aber ohne die Leser zu blockieren. Wie viele Zeilen müssen Sie löschen?
a_horse_with_no_name
@a_horse_with_no_name Normalerweise zwischen 200 und 300.000 Zeilen mit vielen varchar-Spalten. Die Wartezeit von DELETEund INSERTwäre viel zu lang.
Clarkey

Antworten:

20

Richtig, der TRUNCATE TABLE Befehl , den Sie ausführen, "... erhält eine ACCESS EXCLUSIVE-Sperre für jede Tabelle, für die er ausgeführt wird ", sodass im ersten von Ihnen veröffentlichten SQL-Block alle anderen Clients, die versuchen, nach dieser Zeit auf die Tabelle zuzugreifen, blockiert werden, bis Sie INSERTfertig sind und du COMMIT.

Sie können dieselbe Problemumgehung wie in Ihrem MySQL-spezifischen Code verwenden. Postgres unterstützt ungefähr dieselbe Syntax und weist ein ähnliches Sperrverhalten auf. Nämlich:

BEGIN;
-- You probably want to make sure that no one else is
-- INSERT / UPDATE / DELETE'ing from the original table, otherwise
-- those changes may be lost during this switchover process. One way
-- to do that would be via:
-- LOCK TABLE "table" IN ROW EXCLUSIVE mode;
CREATE TABLE "table_new" (LIKE "table");
INSERT INTO "table_new" ...;

-- The ALTER TABLE ... RENAME TO command takes an Access Exclusive lock on "table",
-- but these final few statements should be fast.
ALTER TABLE "table" RENAME TO "table_old";
ALTER TABLE "table_new" RENAME TO "table";
DROP TABLE "table_old";

COMMIT;

Zusätzlicher Bonus: Postgres unterstützt im Gegensatz zu MySQL Transaktions-DDL. Falls Sie also die obige Transaktion ROLLBACKEN müssen, können Sie dies sicher tun.

Josh Kupershmidt
quelle
Ich werde das mal testen, danke für deine Antwort. Wenn ich die von LOCK TABLEIhnen vorgeschlagene Methode verwenden würde, müsste ich sie vor dem erneut entsperren COMMIT, oder wird sie sich selbst entsperren?
Clarkey
1
BEARBEITEN: In dieser Dokumentation wurde die folgende Anweisung gefunden : "Es gibt keinen UNLOCK TABLE-Befehl; Sperren werden immer am Transaktionsende freigegeben."
Clarkey
1
Eine Sache, die hier fehlt, ist alle Einschränkungen, die noch gehören_old
Intellix