Como se implementa corretamente o bloqueio otimista no MySQL?
Nossa equipe deduziu que devemos fazer o item 4 abaixo ou existe o risco de que outro thread possa atualizar a mesma versão do registro, mas gostaríamos de validar que essa é a melhor maneira de fazê-lo.
- Crie um campo de versão na tabela para o qual você deseja usar o bloqueio otimista, por exemplo, nome da coluna = "versão"
- Ao selecionar, inclua a coluna da versão e anote a versão
- Em uma atualização subsequente do registro, a instrução de atualização deve emitir "where version = X", em que X é a versão que recebemos no item 2 e defina o campo de versão durante essa instrução de atualização como X + 1
- Execute um
SELECT FOR UPDATE
no registro que vamos atualizar para serializar quem pode fazer alterações no registro que estamos tentando atualizar.
Para esclarecer, estamos tentando impedir que dois encadeamentos que selecionam o mesmo registro na mesma janela de tempo em que eles capturam a mesma versão do registro substituam uns aos outros, caso tentem atualizar o registro ao mesmo tempo. Acreditamos que, a menos que façamos o número 4, há uma chance de que, se os dois threads inserirem suas respectivas transações ao mesmo tempo (mas ainda não emitiram suas atualizações), quando atualizarem, o segundo segmento que usará o UPDATE ... onde version = X estará operando em dados antigos.
Estamos corretos ao pensar que devemos fazer esse bloqueio pessimista ao atualizar, mesmo usando campos de versão / bloqueio otimista?
SELECT ... FOR UPDATE
ou bloqueio otimista por versão de linha, não por ambos. Veja os detalhes na resposta.Respostas:
Seu desenvolvedor está enganado. Você precisa de um
SELECT ... FOR UPDATE
ou outro controle de versão, não de ambos.Experimente e veja. Abertas três sessões MySQL
(A)
,(B)
e(C)
para o mesmo banco de dados.Em
(C)
questão:Em ambos
(A)
e(B)
emita umUPDATE
que testa e define a versão da linha, alterando owinner
texto em cada um para que você possa ver qual sessão é qual:Agora
(C)
,UNLOCK TABLES;
para liberar a trava.(A)
e(B)
vai correr pelo bloqueio da linha. Um deles vai ganhar e conseguir o bloqueio. O outro irá bloquear na fechadura. O vencedor que conseguiu o bloqueio irá mudar de linha. Supondo que(A)
seja o vencedor, agora você pode ver a linha alterada (ainda não confirmada e não visível para outras transações) com aSELECT * FROM test WHERE id = 1
.Agora,
COMMIT
na sessão do vencedor, digamos(A)
.(B)
obterá o bloqueio e prosseguirá com a atualização. No entanto, a versão não corresponde mais e, portanto, não altera as linhas, conforme relatado pelo resultado da contagem de linhas. Apenas umUPDATE
teve algum efeito, e o aplicativo cliente pode ver claramente qualUPDATE
teve êxito e qual falhou. Nenhum bloqueio adicional é necessário.Veja os logs da sessão em pastebin aqui . Eu usei
mysql --prompt="A> "
etc para facilitar a diferença entre as sessões. Copiei e colei a saída intercalada na sequência de tempo, portanto, não é uma saída totalmente bruta e é possível que eu tenha cometido erros ao copiar e colar. Teste você mesmo para ver.Se você tivesse não adicionou um campo versão de linha, então você precisa
SELECT ... FOR UPDATE
para ser capaz de garantir de forma confiável ordenação.Se você pensar bem, a
SELECT ... FOR UPDATE
é completamente redundante se você estiver imediatamente fazendo umaUPDATE
reutilização de dados doSELECT
, ou se você estiver usando o controle de versão de linha. OUPDATE
bloqueio será bloqueado de qualquer maneira. Se alguém atualizar a linha entre a leitura e a gravação subsequente, sua versão não corresponderá mais, portanto a atualização falhará. É assim que o bloqueio otimista funciona.O objetivo de
SELECT ... FOR UPDATE
é:SERIALIZABLE
isolamento ou controle de versão de linha.Você não precisa usar o bloqueio otimista (versão de linha) e
SELECT ... FOR UPDATE
. Use um ou outro.fonte
Sem bloqueios (sem tabela, sem transação) necessários ou mesmo desejados:
fonte