Fornecerei uma resposta do ponto de vista do desenvolvedor.
Na minha opinião, quando você encontra uma disputa de linha como a que você descreve, é porque você tem um bug no seu aplicativo. Na maioria dos casos, esse tipo de contenção é um sinal de vulnerabilidade de atualização perdida. Este tópico no AskTom explica o conceito de uma atualização perdida:
Uma atualização perdida acontece quando:
sessão 1: leia o registro de funcionários de Tom
sessão 2: leia o registro de funcionários de Tom
sessão 1: atualizar o registro de funcionários de Tom
sessão 2: atualizar o registro de funcionários de Tom
A Sessão 2 ESCREVERÁ AS GRAVAÇÕES da sessão 1 sem nunca vê-las - resultando em uma atualização perdida.
Você experimentou um efeito colateral desagradável de atualização perdida: a sessão 2 pode ser bloqueada porque a sessão 1 ainda não foi confirmada. O principal problema, porém, é que a sessão 2 atualiza cegamente o registro. Suponha que ambas as sessões emitam a declaração:
UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk
Após as duas instruções, as modificações da sessão1 foram substituídas, sem que a sessão2 tenha sido notificada de que a linha foi modificada pela sessão 1.
A atualização perdida (e o efeito colateral da disputa) nunca deve acontecer, pois é 100% evitável. Você deve usar o bloqueio para evitá-los com dois métodos principais: bloqueio otimista e pessimista .
1) Bloqueio pessimista
Você deseja atualizar uma linha. Nesse modo, você impedirá que outras pessoas modifiquem essa linha solicitando um bloqueio nessa linha (SELECT ... FOR UPDATE NOWAIT instrução). Se a linha já estiver sendo modificada, você receberá uma mensagem de erro, que pode ser traduzida normalmente para o usuário final (esta linha está sendo modificada por outro usuário). Se a linha estiver disponível, faça suas modificações (UPDATE) e confirme sempre que sua transação for concluída.
2) Bloqueio otimista
Você deseja atualizar uma linha. No entanto, você não deseja manter um bloqueio nessa linha, talvez porque use várias transações para atualizar a linha (aplicativo sem estado na Web) ou talvez não queira que nenhum usuário mantenha um bloqueio por muito tempo ( o que pode resultar no bloqueio de outras pessoas). Nesse caso, você não solicitará um bloqueio imediatamente. Você usará um marcador para garantir que a linha não seja alterada quando sua atualização for emitida. Você pode armazenar em cache o valor de todas as colunas ou usar uma coluna de carimbo de data / hora atualizada automaticamente ou uma coluna baseada em sequência. Seja qual for a sua escolha, quando você estiver prestes a executar sua atualização, verifique se o marcador nessa linha não foi alterado emitindo uma consulta como:
SELECT <...>
FROM table
WHERE id = :id
AND marker = :marker
FOR UPDATE NOWAIT
Se a consulta retornar uma linha, faça sua atualização. Caso contrário, isso significa que alguém modificou a linha desde a última vez que você a consultou. Você terá que reiniciar o processo desde o início.
Nota: Se você tiver uma confiança completa em todos os aplicativos que acessam seu banco de dados, poderá confiar em uma atualização direta para o bloqueio otimista. Você pode emitir diretamente:
UPDATE table
SET <...>,
marker = marker + 1
WHERE id = :id;
Se a instrução não atualizar nenhuma linha, você saberá que alguém alterou essa linha e precisará começar tudo de novo.
Se todos os aplicativos concordarem com esse esquema, você nunca seria bloqueado por outra pessoa e evitaria a atualização cega. No entanto, se você não bloquear a linha antecipadamente, ainda estará suscetível ao bloqueio indefinido se outro aplicativo, trabalho em lote ou atualização direta não implementar o bloqueio otimista. É por isso que aconselho sempre bloquear a linha, qualquer que seja sua opção de esquema de bloqueio (a ocorrência de desempenho pode ser insignificante, pois você recupera todos os valores, incluindo o rowid ao bloquear a linha).
TL; DR
- A atualização de uma linha sem bloquear previamente expõe o aplicativo a um possível "congelamento". Isso pode ser evitado se todo o DML no banco de dados implementar bloqueio otimista ou pessimista.
- Verifique se a instrução SELECT retorna valores consistentes com qualquer SELECT anterior (para evitar qualquer problema de atualização perdida)