PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Atomic UPDATE .. SELECT no Postgres


Embora a sugestão de Erwin seja possivelmente a mais simples maneira de obter o comportamento correto (desde que você tente novamente sua transação se receber uma exceção com SQLSTATE de 40001), os aplicativos de enfileiramento, por sua natureza, tendem a funcionar melhor com o bloqueio de requisições por uma chance de ter sua vez na fila do que com a implementação do PostgreSQL de SERIALIZABLE transações, o que permite maior simultaneidade e é um pouco mais "otimista" sobre as chances de colisão.

A consulta de exemplo na pergunta, como está, no padrão READ COMMITTED O nível de isolamento da transação permitiria duas (ou mais) conexões simultâneas para "reivindicar" a mesma linha da fila. O que vai acontecer é isso:
  • T1 inicia e chega até bloquear a linha no UPDATE fase.
  • T2 sobrepõe T1 no tempo de execução e tenta atualizar essa linha. Bloqueia pendente do COMMIT ou ROLLBACK de T1.
  • T1 confirma, tendo "reivindicado" a linha com sucesso.
  • T2 tenta atualizar a linha, descobre que T1 já tem, procura a nova versão da linha, descobre que ela ainda satisfaz os critérios de seleção (que é apenas esse id correspondências) e também "reivindica" a linha.

Ele pode ser modificado para funcionar corretamente (se você estiver usando uma versão do PostgreSQL que permite o FOR UPDATE cláusula em uma subconsulta). Basta adicionar FOR UPDATE ao final da subconsulta que seleciona o id, e isso acontecerá:
  • T1 inicia e agora bloqueia a linha antes de selecionar o código.
  • T2 sobrepõe T1 em tempo de execução e bloqueia ao tentar selecionar um id, aguardando o COMMIT ou ROLLBACK de T1.
  • T1 confirma, tendo "reivindicado" a linha com sucesso.
  • No momento em que T2 for capaz de ler a linha para ver o id, ele vê que foi reivindicado, então encontra o próximo id disponível.

Na REPEATABLE READ ou SERIALIZABLE nível de isolamento de transação, o conflito de gravação geraria um erro, que você poderia detectar e determinar que era uma falha de serialização com base no SQLSTATE e tentar novamente.

Se você geralmente deseja transações SERIALIZABLE, mas deseja evitar novas tentativas na área de enfileiramento, poderá fazer isso usando um bloqueio consultivo.