Eu tenho dois processos que executam código como este em paralelo:
begin;
update foos set unread=false where owner_id=123 and unread=true;
commit;
Isso resulta em impasses.
Meu entendimento do que causa deadlocks é como o cenário descrito nesta pergunta , com instruções UPDATE "entrelaçadas" atualizando duas linhas diferentes em uma ordem diferente. Eu não entendo como uma única instrução UPDATE pode resultar em um impasse. Não consigo replicar o cenário de deadlock usando duas sessões paralelas do psql no meu ambiente de desenvolvimento. Meus palpites de por que não posso replicá-lo:
- Estou entendendo mal meu código que cria o erro de deadlock e, na verdade, existem várias instruções UPDATE em cada transação
- O aspecto "entrelaçado" está acontecendo, mas "dentro" da instrução UPDATE que cobre várias linhas, por isso é difícil replicar.
É possível que este único UPDATE esteja criando o impasse?
Sua instrução modifica várias linhas. Cada uma dessas linhas é bloqueada quando é atualizada.
É bem possível que uma instrução em uma transação simultânea já tenha bloqueado uma dessas linhas, bloqueando seu arquivo
UPDATE
. Se a transação simultânea tentar bloquear uma das linhas que vocêUPDATE
já bloqueou, você obterá um deadlock.Laurenz explicou o mecanismo que pode levar a impasses, e você já incluiu um link para uma explicação mais detalhada de Kevin:
Aqui estão as instruções passo a passo de como replicar um deadlock - funciona com plain
UPDATE
da mesma maneira que comSELECT .. FOR UPDATE
:Agora, como evitar o problema ?
Se você for atualizar uma parte substancial ou toda a tabela - e puder - apenas bloqueie a gravação da tabela . Normalmente, este não é o caminho a percorrer. Caso contrário, três abordagens diferentes:
1. Ordem consistente
O manual tem este conselho no capítulo sobre deadlocks:
Não tenho certeza por que ainda não há
ORDER BY
paraUPDATE
. Mas é com isso que temos que trabalhar. Bloqueie linhasSELECT ... FOR UPDATE
na mesma transação - como você já tentou, como indica sua pergunta anterior . Você acabou de esquecer o determinísticoORDER BY
essencial :Obviamente, todas as transações potencialmente concorrentes precisam adquirir bloqueios na mesma ordem.
2. Pular linhas bloqueadas
Processe apenas linhas desbloqueadas:
Se você tiver certeza de que as linhas ignoradas foram processadas por uma transação concorrente fazendo o mesmo, você terminou aqui. (Tem certeza?)
Caso contrário, para ter certeza, faça uma verificação:
Escritores não bloqueiam leitores e leitores não bloqueiam escritores, então isso retorna
TRUE
até que cada última linha tenha sido atualizada com sucesso. Faça um loop no bloco acimaUPDATE
seguido por este (com o atraso apropriado) até obterFALSE
. Então você está feito.Pode ser mais barato para grandes conjuntos onde
ORDER BY
adicionaria um custo significativo. OTOH, ainda pode fazer sentido adicionarORDER BY
se houver um índice correspondente ...3. Um de cada vez
Semelhante ao acima, exceto que apenas uma única linha é atualizada por vez. Normalmente mais caro, mas qualquer potencial de impasse é eliminado - se feito corretamente. Considere isso quando o processamento de uma única linha já leva muito tempo.
Explicação detalhada (principalmente também aplicável ao acima) e instruções: