Não entendo o que Craig Ringer quis dizer quando comentou:
Esta solução está sujeita a atualizações perdidas se a transação de inserção reverter; não há verificação para impor que o UPDATE afetou todas as linhas.
em https://stackoverflow.com/a/8702291/14731 . Forneça uma sequência de eventos de exemplo (por exemplo, o Thread 1 faz X, o Thread 2 faz Y) que demonstra como as atualizações perdidas podem ocorrer.
postgresql
concurrency
cte
upsert
Gili
fonte
fonte
Respostas:
Acho que provavelmente pretendia acrescentar esse comentário à resposta anterior, sobre duas declarações separadas. Foi há mais de um ano, então não tenho mais certeza.
A consulta baseada no wCTE realmente não resolve o problema que deveria, mas após revisá-la mais de um ano depois, não vejo a possibilidade de atualizações perdidas na versão do wCTE.
(Observe que todas essas soluções só funcionarão bem se você tentar alterar exatamente uma linha com cada transação. Assim que você tentar fazer várias alterações em uma transação, as coisas ficarão complicadas devido à necessidade de repetir loops nas reversões. No mínimo você precisaria usar um ponto de salvamento entre cada alteração.)
Versão de duas instruções sujeita a atualizações perdidas.
A versão que usa duas instruções separadas está sujeita a atualizações perdidas, a menos que o aplicativo verifique a contagem de linhas afetadas da
UPDATE
instrução e aINSERT
instrução e tente novamente se ambas forem zero.Imagine o que acontece se você tiver duas transações
READ COMMITTED
isoladas.UPDATE
(sem efeito)INSERT
(insere uma linha)UPDATE
(sem efeito, a linha inserida por TX1 ainda não está visível)COMMIT
s.INSERT
*, que obtém um novo instantâneo que pode ver a linha confirmada por TX1. AEXISTS
cláusula retorna true, porque o TX2 agora pode ver a linha inserida pelo TX1.Então TX2 não tem efeito. A menos que o aplicativo verifique o número de linhas da atualização, a inserção e as tentativas novamente, se ambos reportarem zero linhas, ele não saberá que a transação não teve efeito e continuará alegremente.
A única maneira de verificar as contas de linha afetadas é executá-la como duas instruções separadas, em vez de uma multi-instrução, ou usar um procedimento.
Você pode usar o
SERIALIZABLE
isolamento, mas ainda precisará de um loop de repetição para lidar com falhas de serialização.A versão do wCTE protege contra o problema de atualizações perdidas porque
INSERT
depende de seUPDATE
afeta alguma linha, em vez de uma consulta separada.O wCTE não elimina violações exclusivas
A versão gravável do CTE ainda não é um upsert confiável.
Considere duas transações que executam isso simultaneamente.
Ambos executam a cláusula VALUES.
Agora, os dois executam a
UPDATE
parte. Como não há linhas correspondentes àUPDATE
cláusula s where, os dois retornam um conjunto de resultados vazio da atualização e não fazem alterações.Agora ambos executam a
INSERT
parte. Como oUPDATE
retorno de zero linhas para as duas consultas, ambas tentamINSERT
a linha.Um consegue. Um lança uma violação única e aborta.
Isso não é motivo de preocupação com a perda de dados, desde que o aplicativo verifique os resultados de erro de suas consultas (ou seja, qualquer aplicativo decentemente escrito) e tente novamente, mas torna a solução não melhor do que as versões de duas instruções existentes. Não elimina a necessidade de um loop de repetição.
A vantagem que o wCTE oferece sobre a versão de duas instruções existente é que ele usa a saída da
UPDATE
para decidir se deveINSERT
, em vez de usar uma consulta separada na tabela. Isso é parcialmente uma otimização, mas em parte protege contra um problema com a versão de duas instruções que causa atualizações perdidas; ver abaixo.Você pode executar o wCTE
SERIALIZABLE
isoladamente, mas obterá falhas de serialização em vez de violações exclusivas. Isso não mudará a necessidade de um loop de repetição.O wCTE não parece vulnerável a atualizações perdidas
Meu comentário sugeriu que essa solução poderia resultar em atualizações perdidas, mas, ao revisar, acho que posso estar enganado.
Já faz mais de um ano, e não me lembro das circunstâncias exatas, mas acho que provavelmente perdi o fato de que índices únicos têm uma exceção parcial das regras de visibilidade de transações, a fim de permitir que uma transação de inserção espere a outra inserir ou rolar antes de prosseguir.
Ou talvez eu tenha perdido o fato de que o
INSERT
wCTE depende de se asUPDATE
linhas afetaram alguma, e não da linha candidata existir na tabela.INSERT
S conflitantes em uma espera de índice exclusiva para confirmação / reversãoDigamos que uma cópia da consulta seja executada, inserindo uma linha. A mudança ainda não foi confirmada. A nova tupla existe no heap e no índice exclusivo, mas ainda não está visível para outras transações, independentemente dos níveis de isolamento.
Agora outra cópia da consulta é executada. A linha inserida ainda não está visível, pois a primeira cópia não foi confirmada; portanto, a atualização não corresponde a nada. A consulta continuará tentando inserir, o que verá que outra transação em andamento está inserindo a mesma chave e bloqueará a espera pela confirmação ou reversão dessa transação .
Se a primeira transação for confirmada, a segunda falhará com uma violação única, conforme o descrito acima. Se a primeira transação reverter, a segunda continuará com sua inserção.
O
INSERT
ser dependente doUPDATE
número de linhas protege contra atualizações perdidasAo contrário do caso de duas declarações, não acho que o wCTE seja vulnerável a atualizações perdidas.
Se o
UPDATE
não tiver efeito, oINSERT
sempre será executado, porque é estritamente condicional se oUPDATE
item fez alguma coisa, não no estado da tabela externa. Portanto, ainda pode falhar com uma violação única, mas silenciosamente não pode ter nenhum efeito e perder a atualização completamente.fonte