Come sostituire atomicamente i dati della tabella in PostgreSQL


14

Voglio sostituire l'intero contenuto di una tabella, senza influire sulle SELECTdichiarazioni in entrata durante il processo.

Il caso d'uso è avere una tabella che memorizzi le informazioni sulla cassetta postale che vengono regolarmente estratte e che devono essere archiviate in una tabella PostgreSQL. Esistono molti client che utilizzano un'applicazione che interroga costantemente quella stessa tabella.

Normalmente, farei qualcosa del genere (pseudocodice in arrivo) ...

BEGIN TRANSACTION
TRUNCATE TABLE
INSERT INTO
COMMIT

Ma sfortunatamente la tabella non può essere letta durante questo processo; a causa del tempo necessario INSERT INTOper il completamento. Il tavolo è bloccato.

In MySQL, avrei usato il loro RENAME TABLEcomando atomico per evitare questi problemi ...

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;

Come ho potuto raggiungere questo obiettivo in PostgreSQL?

Ai fini di questa domanda, puoi presumere che non sto usando chiavi esterne.


Perché pensi che la tabella non possa essere letta durante l'inserimento di righe in essa? La tabella troncata avrà effetto immediato in tutte le sessioni; tuttavia, gli inserti (se eseguiti all'interno di una transazione che li avvolge tutti, come suggerisce il tuo pseudo-codice) non saranno visibili ad altre sessioni fino al momento del commit. Altre sessioni saranno in grado di selezionare dalla tabella e vedranno una tabella vuota fino al momento del commit.
zgguy,

2
@zgguy il TRUNCATEcomando acquisirà un blocco AccessExclusive sulla tabella, quindi nessun altro sarà in grado di leggere dalla tabella fino a quando la transazione non viene confermata o non viene ripristinata.
Josh Kupershmidt,

2
Se lo usi deleteinvece truncatesarà più lento, ma senza bloccare i lettori. Quante righe devi eliminare?
a_horse_with_no_name

@a_horse_with_no_name Di solito tra 200-300k righe con molte colonne varchar. Il tempo di attesa di DELETEe INSERTsarebbe troppo lungo.
Clarkey,

Risposte:


20

Bene, il TRUNCATE TABLE comando che stai eseguendo "... acquisisce un blocco ACCESS EXCLUSIVE su ogni tabella su cui opera ", quindi nel primo blocco SQL che hai pubblicato, tutti gli altri client che tentano di accedere alla tabella dopo quel tempo saranno bloccati fino al INSERTtermine e tu COMMIT.

È possibile utilizzare la stessa soluzione alternativa nel codice specifico di MySQL; Postgres supporta all'incirca la stessa sintassi e avrà un comportamento di blocco simile. Per dire:

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;

Bonus extra: Postgres supporta effettivamente il DDL transazionale, a differenza di MySQL, quindi nel caso in cui tu debba ROLLBACK la transazione sopra, puoi farlo tranquillamente.


Farò dei test su questo, grazie per la tua risposta. Se avessi usato il LOCK TABLEmetodo che mi hai suggerito, avrei bisogno di sbloccarlo di nuovo prima COMMITo si sbloccherà da solo?
Clarkey,

1
EDIT: In questa documentazione è stata trovata la seguente dichiarazione : "Non esiste alcun comando UNLOCK TABLE; i blocchi vengono sempre rilasciati al termine della transazione."
Clarkey,

2
Una cosa che manca qui sono tutti i vincoli associati che ancora appartengono_old
Intellix

@Intellix puoi approfondire questo? Significa che i vincoli sono semplicemente denominati per la vecchia tabella o che riguardano solo la vecchia tabella (il che significa che i vincoli vengono effettivamente eliminati)?
Maerici il

Il commento prima della creazione della tabella ( -- LOCK TABLE "table" IN ROW EXCLUSIVE mode;) sembra essere insufficiente a proteggere da un aggiornamento / inserimento nella tabella di origine in base alle specifiche. ROW EXCLUSIVEÈ possibile acquisire due blocchi senza alcun conflitto (consultare la Tabella 13.2 in postgresql.org/docs/10/explicit-locking.html#LOCKING-TABLES ). Per impedire gli aggiornamenti dei dati è necessario almeno un SHAREblocco.
Pilou
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.