Est-il possible de créer une transaction atomique dans PostgreSQL?
Considérez que j'ai une catégorie de table avec ces lignes:
id|name
--|---------
1 |'tablets'
2 |'phones'
Et le nom de la colonne a une contrainte unique.
Si j'essaye:
BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;
Je suis en train:
ERROR: duplicate key value violates unique constraint "category_name_key"
DETAIL: Key (name)=(tablets) already exists.
En plus de ce que @ Craig fourni (et en corriger une partie):
Postgres effectifs 9.4 , UNIQUE
, PRIMARY KEY
et EXCLUDE
les contraintes sont vérifiées immédiatement après chaque ligne lorsqu'elles sont définies NOT DEFERRABLE
. Ceci est différent des autres types de NOT DEFERRABLE
contraintes (actuellement uniquement REFERENCES
(clé étrangère)) qui sont vérifiées après chaque instruction . Nous avons travaillé tout cela sous cette question connexe sur SO:
Ce n'est pas suffisant pour un UNIQUE
(ou PRIMARY KEY
ou EXCLUDE
) contrainte d'être DEFERRABLE
pour que votre code présenté avec plusieurs instructions fonctionne.
Et vous ne pouvez pas utiliser dans ce but. Par documentation:ALTER TABLE ... ALTER CONSTRAINT
ALTER CONSTRAINT
Ce formulaire modifie les attributs d'une contrainte précédemment créée. Actuellement seules les contraintes de clé étrangère peuvent être modifiées .
Accentuation mienne. Utilisez plutôt:
ALTER TABLE t
DROP CONSTRAINT category_name_key
, ADD CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;
Supprimez et rajoutez la contrainte dans une seule instruction afin qu'il n'y ait pas de fenêtre temporelle pour quiconque de se faufiler dans les lignes incriminées. Pour les grandes tables, il serait tentant de conserver l'index unique sous-jacent d'une manière ou d'une autre, car il est coûteux de le supprimer et de le recréer. Hélas, cela ne semble pas possible avec des outils standards (si vous avez une solution pour cela, faites-le nous savoir!):
Pour une seule instruction rendre la contrainte reportable suffit:
UPDATE category c
SET name = c_old.name
FROM category c_old
WHERE c.id IN (1,2)
AND c_old.id IN (1,2)
AND c.id <> c_old.id;
Une requête avec CTE est également une instruction single:
WITH x AS (
UPDATE category SET name = 'phones' WHERE id = 1
)
UPDATE category SET name = 'tablets' WHERE id = 2;
Cependant , pour votre code avec plusieurs instructions vous (en plus) devez reporter la contrainte - ou la définir comme INITIALLY DEFERRED
Soit est généralement plus cher que ce qui précède. Mais il peut ne pas être facile de tout regrouper en une seule instruction.
BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones' WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;
Soyez conscient d'une limitation en relation avec FOREIGN KEY
contraintes, cependant. Par documentation:
Les colonnes référencées doivent être les colonnes d'une contrainte de clé unique ou primaire non différable dans la table référencée.
Vous ne pouvez donc pas avoir les deux en même temps.
Si je comprends bien, votre problème ici est que la contrainte est vérifiée après chaque instruction, mais vous voulez qu'elle soit vérifiée à la fin de la transaction, donc elle compare l'état avant à l'état après, en ignorant les états intermédiaires.
Si c'est le cas, cela est possible avec une contrainte déférable .
Voir SET CONSTRAINTS
et DEFERRABLE
contraintes comme indiqué dans CREATE TABLE
.
Notez que les contraintes différées ont des coûts - le système doit en conserver une liste à vérifier au moment de la validation, elles ne sont donc pas adaptées aux transactions qui apportent d'énormes ensembles de modifications. Ils sont également plus lents à vérifier.
Je pense donc que vous voulez probablement:
ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;
Notez qu'il semble y avoir une limitation sur ALTER TABLE
définition des contraintes sur DEFERRABLE
; vous devrez peut-être à la place DROP
et re -ADD
la contrainte.