É possível criar uma transação atômica no PostgreSQL?
Considere que eu tenho uma categoria de tabela com estas linhas:
id|name
--|---------
1 |'tablets'
2 |'phones'
E o nome da coluna tem restrição exclusiva.
Se eu tentar:
BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;
Estou entendendo:
ERROR: duplicate key value violates unique constraint "category_name_key"
DETAIL: Key (name)=(tablets) already exists.
Além do que o @Craig forneceu (e corrigindo parte dele):
As restrições efetivas do Postgres 9.4 ,
UNIQUE
e são verificadas imediatamente após cada linha quando definidas . Isso é diferente de outros tipos de restrições (atualmente apenas (chave estrangeira)) que são verificadas após cada instrução . Nós trabalhamos tudo isso sob esta questão relacionada no SO:PRIMARY KEY
EXCLUDE
NOT DEFERRABLE
NOT DEFERRABLE
REFERENCES
Não é suficiente que uma restrição
UNIQUE
(orPRIMARY KEY
ou ) seja para fazer seu código apresentado com várias instruções funcionar.EXCLUDE
DEFERRABLE
E você não pode usar
para esta finalidade. Por documentação:ALTER TABLE ... ALTER CONSTRAINT
Ênfase em negrito minha. Em vez disso, use:
Elimine e adicione a restrição de volta em uma única instrução para que não haja janela de tempo para que alguém se infiltre nas linhas ofensivas. Para tabelas grandes, seria tentador conservar o índice exclusivo subjacente de alguma forma, porque é caro excluí-lo e recriá-lo. Infelizmente, isso não parece ser possível com ferramentas padrão (se você tiver uma solução para isso, informe-nos!):
Para uma única declaração tornar a restrição adiável é suficiente:
Uma consulta com CTEs também é uma única instrução:
No entanto , para o seu código com várias instruções, você (adicionalmente) precisa realmente adiar a restrição - ou defini-la como
INITIALLY DEFERRED
qualquer um normalmente é mais caro do que o acima. Mas pode não ser facilmente viável agrupar tudo em uma declaração.Esteja ciente de uma limitação em conexão com as
FOREIGN KEY
restrições, no entanto. Por documentação:Então você não pode ter os dois ao mesmo tempo.
Pelo que entendi, seu problema aqui é que a restrição é verificada após cada instrução, mas você deseja que seja verificada no final da transação, para comparar o estado anterior ao estado posterior, ignorando os estados intermediários.
Nesse caso, isso é possível com uma restrição adiável .
Consulte
SET CONSTRAINTS
eDEFERRABLE
restrições conforme documentado emCREATE TABLE
.Observe que as restrições adiadas têm custos - o sistema precisa manter uma lista delas para verificar no momento do commit, portanto, elas não são boas para transações que fazem grandes conjuntos de alterações. Eles também são mais lentos para verificar.
Então eu acho que você provavelmente quer:
Observe que parece haver uma limitação na
ALTER TABLE
definição de restrições paraDEFERRABLE
; você pode ter queDROP
refazerADD
a restrição.