Quando falamos de arquitetura de sistemas, especialmente em ambientes que lidam com múltiplos usuários acessando e atualizando dados ao mesmo tempo, o grande desafio é evitar inconsistências geradas por concorrência. Situações como dois pedidos chegando simultaneamente para o último item do estoque, dois analistas editando o mesmo registro ou duas rotinas automatizadas tentando alterar o mesmo recurso são extremamente comuns. Para impedir esse tipo de falha, bancos de dados relacionais oferecem dois modelos clássicos de controle de concorrência: Optimistic Locking e Pessimistic Locking.
O Pessimistic Locking funciona com a lógica mais intuitiva possível: antes que alguém modifique um registro, ele é imediatamente bloqueado. Assim que o primeiro processo inicia a transação, o banco impede que qualquer outro processo altere aquela linha até que o commit seja realizado. É como colocar um cadeado temporário no dado. Esse modelo garante que nenhuma operação paralela interfira no que está acontecendo, evitando completamente conflitos. É ideal para cenários críticos — movimentações financeiras, decremento de estoque único, registros que não podem sofrer edição simultânea e operações que dependem de consistência absoluta. O ponto fraco é o custo: enquanto a transação está ativa, outros processos podem ser forçados a esperar, gerando fila e aumentando o tempo de resposta sob alta carga.
Um exemplo clássico de bloqueio pessimista aparece quando realizamos um SELECT ... FOR UPDATE dentro de uma transação:
START TRANSACTION;
SELECT estoque
FROM produtos
WHERE id = 10
FOR UPDATE;
UPDATE produtos
SET estoque = estoque - 1
WHERE id = 10;
INSERT INTO pedidos (produto_id, quantidade)
VALUES (10, 1);
COMMIT;
Nesse fluxo, o primeiro processo que executar o FOR UPDATE garante exclusividade. Qualquer tentativa paralela de tocar o mesmo registro será automaticamente bloqueada até o commit. O resultado é previsível e seguro: nunca haverá oversell.
Enquanto isso, o Optimistic Locking segue uma lógica totalmente diferente. Em vez de bloquear a linha antes de editar, ele assume que conflitos são raros. Vários processos podem ler e editar o mesmo registro simultaneamente, e o banco só verifica se houve alteração no momento do commit. Para isso funcionar, normalmente existe uma coluna de versão, timestamp ou hash que representa o “estado atual” da linha. Quando o processo tenta salvar, ele atualiza apenas se a versão ainda for a mesma que ele leu. Se alguém salvou antes, a atualização falha e um conflito é detectado.
Esse modelo é muito usado em aplicações de grande escala, APIs de alta leitura ou sistemas distribuídos, pois evita a sobrecarga de manter locks ativos. Se dois processos concorrem, apenas o primeiro salva; o segundo recebe um erro e pode tentar novamente ou abortar a operação. O custo está na necessidade de resolver conflitos manualmente ou realizar retries automáticos.
Um exemplo clássico usando versão:
SELECT estoque, version
FROM produtos
WHERE id = 10;
UPDATE produtos
SET estoque = estoque - 1,
version = version + 1
WHERE id = 10
AND version = 5;
Se essa atualização não afetar nenhuma linha, significa que alguém alterou o registro primeiro e o conflito está detectado. A integridade é preservada sem travar nada antecipadamente.
A diferença entre os dois modelos fica ainda mais clara quando pensamos na famosa corrida por um item de estoque único. No modelo pessimista, o primeiro processo trava a linha e o segundo fica esperando até que o banco libere, garantindo que apenas o primeiro consumirá o estoque. No modelo otimista, ambos leem o valor ao mesmo tempo, mas apenas o primeiro a salvar consegue atualizar a versão. O segundo falha com conflito. Nos dois casos, o oversell é evitado; a diferença está na estratégia.
Um exemplo prático usando Laravel e bloqueio pessimista mostra exatamente esse comportamento:
DB::transaction(function () use ($productId) {
$product = DB::table('produtos')
->where('id', $productId)
->lockForUpdate()
->first();
if ($product->estoque < 1) {
throw new Exception("Sem estoque");
}
DB::table('produtos')
->where('id', $productId)
->update([
'estoque' => $product->estoque - 1
]);
DB::table('pedidos')->insert([
'produto_id' => $productId,
'quantidade' => 1
]);
});
Para otimistic locking em Laravel, o exemplo muda completamente, pois a lógica se baseia em comparar versões:
$product = Produto::find($id);
$affected = Produto::where('id', $product->id)
->where('version', $product->version)
->update([
'estoque' => $product->estoque - 1,
'version' => $product->version + 1,
]);
if ($affected === 0) {
throw new Exception("Conflito de concorrência");
}
Ambas as abordagens são válidas e garantem integridade, mas cada uma atende necessidades específicas. O bloqueio pessimista é perfeito quando a segurança absoluta é mais importante que a performance. Já o bloqueio otimista é ideal quando a performance, escalabilidade e baixa contenção são prioridade, especialmente em sistemas distribuídos. A escolha não deve ser feita apenas olhando para o banco, mas sim para o comportamento do sistema como um todo.
No fim, o arquiteto precisa entender o impacto da concorrência no seu domínio. Se o conflito representa risco, custo financeiro ou inconsistência crítica, o pessimista prevalece. Se o sistema tolera retries e conflitos são raros, o modelo otimista entrega melhor performance. Ambos, quando bem aplicados, são fundamentais para garantir operações seguras em ambientes modernos.




