Comment remplacer atomiquement les données d'une table dans PostgreSQL

14

Je souhaite remplacer l'intégralité du contenu d'une table, sans affecter les SELECTinstructions entrantes au cours du processus.

Le cas d'utilisation est d'avoir une table qui stocke des informations de boîte aux lettres qui sont régulièrement extraites et doit être stockée dans une table PostgreSQL. De nombreux clients utilisent une application qui interroge constamment cette même table.

Normalement, je ferais quelque chose comme (pseudocode entrant) ...

BEGIN TRANSACTION
TRUNCATE TABLE
INSERT INTO
COMMIT

Mais malheureusement, le tableau ne peut pas être lu pendant ce processus; en raison du temps qu'il faut INSERT INTOpour terminer. La table est verrouillée.

Dans MySQL, j'aurais utilisé leur RENAME TABLEcommande atomique pour éviter ces problèmes ...

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;

Comment pourrais-je y parvenir dans PostgreSQL?

Aux fins de cette question, vous pouvez supposer que je n'utilise pas de clés étrangères.

Clarkey
la source
Pourquoi pensez-vous que le tableau ne peut pas être lu lors de l'insertion de lignes? La table tronquée aura un effet immédiat sur toutes les sessions; cependant, les insertions (si elles sont effectuées dans une transaction qui les enveloppe toutes, comme le suggère votre pseudo-code) ne seront pas visibles par les autres sessions jusqu'à ce que vous les validiez. Les autres sessions pourront sélectionner dans la table et verront une table vide jusqu'à ce que vous vous engagiez.
zgguy
2
@zgguy la TRUNCATEcommande va acquérir un verrou AccessExclusive sur la table, donc personne d'autre ne pourra lire à partir de la table jusqu'à ce que cette transaction soit validée ou annulée.
Josh Kupershmidt
2
Si vous utilisez à la deleteplace, truncateil sera plus lent, mais sans bloquer les lecteurs. Combien de lignes devez-vous supprimer?
a_horse_with_no_name
@a_horse_with_no_name Généralement entre 200 et 300k lignes avec de nombreuses colonnes varchar. Le temps d'attente DELETEet INSERTserait bien trop long.
Clarkey

Réponses:

20

À droite, la TRUNCATE TABLE commande que vous exécutez "... acquiert un verrou ACCESS EXCLUSIVE sur chaque table sur laquelle il opère ", donc dans le premier bloc SQL que vous avez publié, tous les autres clients tentant d'accéder à la table après cette heure seront bloqués jusqu'à ce que vous ayez INSERTterminé et toi COMMIT.

Vous pouvez utiliser la même solution de contournement que dans votre code spécifique à MySQL; Postgres prend en charge à peu près la même syntaxe et aura un comportement de verrouillage similaire. En être témoin:

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 supplémentaire: Postgres prend en charge le DDL transactionnel, contrairement à MySQL, donc si vous devez ROLLBACK la transaction ci-dessus, vous pouvez le faire en toute sécurité.

Josh Kupershmidt
la source
Je vais faire des tests à ce sujet, merci pour votre réponse. Si j'ai utilisé la LOCK TABLEméthode que vous avez suggérée, devrais-je la déverrouiller à nouveau avant le COMMIT, ou va-t-elle se déverrouiller elle-même?
Clarkey
1
EDIT: a trouvé l'énoncé suivant dans cette documentation : "Il n'y a pas de commande UNLOCK TABLE; les verrous sont toujours libérés à la fin de la transaction."
Clarkey
2
Une chose manque ici, c'est toutes les contraintes attachées qui appartiennent toujours à_old
Intellix
@Intellix pouvez-vous nous en dire plus? Cela signifie-t-il que les contraintes sont simplement nommées d'après l'ancienne table ou qu'elles ne concernent que l'ancienne table (ce qui signifie que les contraintes sont effectivement supprimées)?
maerics
Le commentaire avant la création de la table ( -- LOCK TABLE "table" IN ROW EXCLUSIVE mode;) semble insuffisant pour se protéger d'une mise à jour / insertion dans la table source selon les spécifications. Deux ROW EXCLUSIVEverrous peuvent être acquis sans conflit (consultez le tableau 13.2 dans postgresql.org/docs/10/explicit-locking.html#LOCKING-TABLES ). Afin d'éviter les mises à jour des données, vous avez besoin d'au moins un SHAREverrou.
Pilou