Por que o CTE está aberto a atualizações perdidas?

8

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.

Gili
fonte
11
Pergunte-me sobre um comentário que deixei mais de um ano atrás sobre um assunto complexo ... divertido! Agora eu tenho que lembrar qual era o problema exato. Reexaminando-o agora.
Craig Ringer

Respostas:

14

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 UPDATEinstrução e a INSERTinstrução e tente novamente se ambas forem zero.

Imagine o que acontece se você tiver duas transações READ COMMITTEDisoladas.

  • TX1 executa o UPDATE(sem efeito)
  • TX1 executa o INSERT(insere uma linha)
  • TX2 executa o UPDATE(sem efeito, a linha inserida por TX1 ainda não está visível)
  • TX1 COMMITs.
  • TX2 executa o INSERT*, que obtém um novo instantâneo que pode ver a linha confirmada por TX1. A EXISTSclá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 SERIALIZABLEisolamento, 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 INSERTdepende de se UPDATEafeta 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 UPDATEparte. Como não há linhas correspondentes à UPDATEcláusula s where, os dois retornam um conjunto de resultados vazio da atualização e não fazem alterações.

  • Agora ambos executam a INSERTparte. Como o UPDATEretorno de zero linhas para as duas consultas, ambas tentam INSERTa 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 UPDATEpara decidir se deve INSERT, 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 SERIALIZABLEisoladamente, 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 INSERTwCTE depende de se as UPDATElinhas afetaram alguma, e não da linha candidata existir na tabela.

INSERTS conflitantes em uma espera de índice exclusiva para confirmação / reversão

Digamos 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 INSERTser dependente do UPDATEnúmero de linhas protege contra atualizações perdidas

Ao contrário do caso de duas declarações, não acho que o wCTE seja vulnerável a atualizações perdidas.

Se o UPDATEnão tiver efeito, o INSERTsempre será executado, porque é estritamente condicional se o UPDATEitem 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.

Craig Ringer
fonte