Jak atomowo zastąpić dane tabeli w PostgreSQL

14

Chcę zastąpić całą zawartość tabeli, nie wpływając na żadne przychodzące SELECTinstrukcje podczas procesu.

Przypadkiem użycia jest posiadanie tabeli, która przechowuje informacje o skrzynce pocztowej, które są regularnie wyodrębniane i muszą być przechowywane w tabeli PostgreSQL. Jest wielu klientów korzystających z aplikacji, która stale odpytuje tę samą tabelę.

Normalnie zrobiłbym coś takiego (przychodzący pseudokod) ...

BEGIN TRANSACTION
TRUNCATE TABLE
INSERT INTO
COMMIT

Niestety nie można odczytać tabeli podczas tego procesu; ze względu na czas potrzebny INSERT INTOdo ukończenia. Stół jest zamknięty.

W MySQL użyłbym ich RENAME TABLEpolecenia atomowego, aby uniknąć tych problemów ...

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;

Jak mogę to osiągnąć w PostgreSQL?

Na potrzeby tego pytania możesz założyć, że nie używam kluczy obcych.

Clarkey
źródło
Jak myślisz, dlaczego nie można odczytać tabeli podczas wstawiania w niej wierszy? Obcięta tabela będzie miała natychmiastowy efekt we wszystkich sesjach; jednak wstawki (jeśli są wykonywane w transakcji, która je wszystkie otacza, jak sugeruje pseudo-kod), nie będą widoczne dla innych sesji, dopóki nie zatwierdzisz. Inne sesje będą mogły wybierać z tabeli i będą widzieć pustą tabelę do momentu zatwierdzenia.
zgguy
2
@zgguy TRUNCATEpolecenie uzyska blokadę AccessExclusive na stole, więc nikt inny nie będzie mógł odczytać z tabeli, dopóki transakcja nie zostanie zatwierdzona lub wycofana.
Josh Kupershmidt
2
Jeśli użyjesz deletezamiast tego truncate, będzie wolniejszy, ale bez blokowania czytników. Ile wierszy musisz usunąć?
a_horse_w_no_name
@ a_horse_w_no_name Zwykle między 200-300k wierszy z wieloma kolumnami varchar. Czas oczekiwania DELETEi INSERTbyłby zdecydowanie za długi.
Clarkey,

Odpowiedzi:

21

Tak, TRUNCATE TABLE polecenie , które wykonujesz „... uzyskuje blokadę WYŁĄCZNIE DOSTĘPU dla każdej tabeli, na której działa ”, więc w pierwszym wysłanym bloku SQL wszyscy inni klienci próbujący uzyskać dostęp do tabeli po tym czasie będą blokowani do czasu INSERTzakończenia a COMMIT.

Możesz użyć tego samego obejścia, co w kodzie specyficznym dla MySQL; Postgres obsługuje mniej więcej tę samą składnię i będzie miał podobne zachowanie blokowania. To znaczy:

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 SHARE 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;

Dodatkowy bonus: Postgres faktycznie obsługuje transakcyjny DDL, w przeciwieństwie do MySQL, więc w razie potrzeby ODWRÓCENIA powyższej transakcji, możesz to zrobić bezpiecznie.

Josh Kupershmidt
źródło
Mam zamiar przeprowadzić kilka testów, dzięki za odpowiedź. Gdybym użył LOCK TABLEmetody, którą zasugerowałeś, czy musiałbym ją odblokować przed COMMIT, czy sam się odblokuje?
Clarkey,
1
EDYCJA: Znaleziono następującą instrukcję w tej dokumentacji : „Nie ma polecenia ODBLOKUJ TABELĘ; blokady są zawsze zwalniane po zakończeniu transakcji”.
Clarkey,
2
Brakuje tutaj tylko jednego z załączonych ograniczeń, które wciąż należą do_old
Intellix
@Intellix, czy możesz to rozwinąć? Czy to oznacza, że ​​ograniczenia są po prostu nazywane dla starej tabeli lub że odnoszą się tylko do starej tabeli (co oznacza, że ​​ograniczenia są skutecznie usuwane)?
maerics
Komentarz przed utworzeniem tabeli ( -- LOCK TABLE "table" IN ROW EXCLUSIVE mode;) wydaje się być niewystarczający do ochrony przed aktualizacją / wstawieniem do tabeli źródłowej zgodnie ze specyfikacją. Dwie ROW EXCLUSIVEblokady można uzyskać bez żadnego konfliktu (patrz Tabela 13.2 w postgresql.org/docs/10/explicit-locking.html#LOCKING-TABLES ). Aby zapobiec aktualizacjom danych, potrzebujesz przynajmniej SHAREblokady.
Pilou