Evita violazioni univoche nelle transazioni atomiche


15

È possibile creare una transazione atomica in PostgreSQL?

Considera che ho una categoria di tabella con queste righe:

id|name
--|---------
1 |'tablets'
2 |'phones'

E il nome della colonna ha un vincolo univoco.

Se provo:

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;

Sto ottenendo:

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.

Risposte:


24

Oltre a quanto fornito da @Craig (e correggendone alcuni):

Efficace Postgres 9.4 , UNIQUE, PRIMARY KEYe EXCLUDEvincoli vengono controllati immediatamente dopo ogni riga quando definito NOT DEFERRABLE. Questo è diverso da altri tipi di NOT DEFERRABLEvincoli (attualmente solo REFERENCES(chiave esterna)) che vengono controllati dopo ogni istruzione . Abbiamo risolto tutto questo con questa domanda correlata su SO:

E ' non è sufficiente per una UNIQUE(o PRIMARY KEYo EXCLUDE) vincolo di essere DEFERRABLEper rendere il codice presentato con più istruzioni di lavoro.

Ed è possibile non utilizzare ALTER TABLE ... ALTER CONSTRAINTper questo scopo. Per documentazione:

ALTER CONSTRAINT

Questo modulo modifica gli attributi di un vincolo precedentemente creato. Attualmente è possibile modificare solo i vincoli di chiave esterna .

Enorme enfasi sulla mia. Usa invece:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

Rilascia e aggiungi nuovamente il vincolo in una singola istruzione in modo che non ci sia tempo per nessuno che si intrufoli nelle file offensive. Per le tabelle di grandi dimensioni sarebbe in qualche modo allettante conservare l'indice univoco sottostante, in quanto è costoso eliminarlo e ricrearlo. Purtroppo, ciò non sembra possibile con gli strumenti standard (se hai una soluzione per questo, faccelo sapere!):

Per una singola affermazione è sufficiente rendere differibile il vincolo:

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;

Una query con CTE è anche una singola istruzione:

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

Tuttavia , per il tuo codice con più istruzioni devi (in aggiunta) effettivamente rinviare il vincolo o definirlo come INITIALLY DEFERREDO è in genere più costoso di quanto sopra. Ma potrebbe non essere facilmente fattibile racchiudere tutto in un'unica istruzione.

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;

Essere consapevoli di una limitazione in relazione ai FOREIGN KEYvincoli, però. Per documentazione:

Le colonne di riferimento devono essere le colonne di un vincolo di chiave unica o primaria non differibile nella tabella di riferimento.

Quindi non puoi avere entrambi allo stesso tempo.


13

A quanto ho capito, il tuo problema qui è che il vincolo viene verificato dopo ogni istruzione, ma vuoi che venga verificato alla fine della transazione, quindi confronta lo stato precedente con quello successivo, ignorando gli stati intermedi.

In tal caso, ciò è possibile con un vincolo differibile .

Vedi SET CONSTRAINTSe DEFERRABLEvincoli come documentato in CREATE TABLE.

Si noti che i vincoli differiti hanno dei costi: il sistema deve tenerne un elenco per verificarli al momento del commit, quindi non sono utili per le transazioni che apportano enormi serie di modifiche. Sono anche più lenti da controllare.

Quindi penso che probabilmente vorrai:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

Si noti che sembra esserci una limitazione ALTER TABLEsull'impostazione dei vincoli su DEFERRABLE; potresti invece dover DROPe ri ADD-vincolare il vincolo.

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.