Digamos que temos a seguinte situação em um aplicativo usando MySQL onde um usuário pode comprar um item e cada item tem um único comprador, mas o preço do item pode mudar.
Pseudo-código :
BEGIN TRANSACTION
seenPrice = SELECT price FROM Item
WHERE id = ABC AND buyer IS NULL
UPDATE Item SET buyer = X
WHERE id = ABC AND buyer IS NULL AND price = seenPrice
COMMIT
A seenPrice
variável e as buyer IS NULL AND price = seenPrice
verificações na instrução UPDATE servem como um meio de bloqueio otimista para garantir que nenhum problema de simultaneidade apareça .
Em um ambiente multi-threaded onde um thread A e um thread B passam pela SELECT
instrução ao mesmo tempo e dizem que o thread A executa a UPDATE
instrução primeiro, a simultaneidade não é uma preocupação , mas é possível que A e B executem a UPDATE
instrução no mesmo tempo exato? Para contextualizar o aplicativo está sendo desenvolvido usando Spring Boot e Spring Data JPA.
As instruções podem começar a ser executadas ao mesmo tempo, mas não podem atualizar a mesma linha simultaneamente. Uma instrução de atualização deve adquirir um bloqueio exclusivo na linha antes que possa ser modificada, e a aquisição de bloqueio é uma operação atômica estritamente serializada, o que significa que uma das sessões simultâneas sempre adquirirá o bloqueio primeiro e as outras sessões serão bloqueadas .
A aquisição de bloqueio é sempre atômica, independentemente do DBMS ou mecanismo de armazenamento - se não fosse, seria completamente inútil como mecanismo de controle de simultaneidade.
O mecanismo de bloqueio não se importa se seu aplicativo é escrito usando uma abordagem de bloqueio otimista ou pessimista - o que muda é o momento em que o bloqueio é feito. Com o bloqueio pessimista, você espera que haja muita atividade simultânea e deseja garantir que sua transação seja bem-sucedida (ao custo de impedir atualizações simultâneas):
SELECT ... FOR UPDATE
UPDATE ...
COMMIT
Com o bloqueio otimista, você não espera muita atividade simultânea, portanto, a probabilidade de seu aplicativo não concluir a atualização é baixa e você não mantém o bloqueio por muito tempo:
SELECT ...
UPDATE ...
COMMIT
Não, não é possível que A e B executem o UPDATE exatamente ao mesmo tempo. Um ou outro adquirirá o bloqueio na linha primeiro e, em seguida, o outro será bloqueado até que o vencedor se comprometa.
Uma vez que o vencedor se compromete, a condição
buyer IS NULL
não será mais verdadeira, então o UPDATE da thread que esperou não a afetará.Podemos fazer um experimento para testar isso:
Na janela 1:
Na janela 2, faça o mesmo, inicie uma transação e visualize o preço.
Na janela 1:
Na janela 2:
Na janela 1:
Na janela 2:
Observe zero linhas correspondidas ou alteradas! Porque o comprador não era mais nulo na versão confirmada mais recente da linha.
Como você está testando
buyer IS NULL
em ambas as instruções, o processamento "funcionará". No entanto, uma conexão diferente pode se infiltrar entre oSELECT
andUPDATE
e setbuyer
.Então, você deve verificar para ver se o
UPDATE
realmente foi bem-sucedido. Se não, então o que? Diga ao usuário "Desculpe, você passou por toda a interface do usuário apenas para que outra pessoa entrasse e comprasse o que você queria". Eca.Em vez disso, faça
FOR UPDATE
noSELECT
. Dessa forma, você pode detectar o problema mais cedo.E não escreva código que fique preso a uma transação por mais de alguns segundos. Isso pode levar a todos os tipos de colapsos desagradáveis em seu aplicativo.