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
ouROLLBACK
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
ouROLLBACK
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.