Marcio Cunha

Processamento Concorrente de Transações Financeiras com Locks Otimistas em Bancos de Dados Relacionais

Descubra como garantir consistência em saldos bancários sem travar o banco de dados. Entenda o funcionamento do bloqueio otimista e como aplicá-lo em cenários de alta concorrência.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • O bloqueio otimista assume que colisões em registros financeiros são raras, permitindo leituras simultâneas sem travar linhas na tabela.
  • O uso de colunas de versão ou carimbos de data e hora impede que atualizações concorrentes sobrescrevam valores de forma silenciosa.
  • Sistemas de pagamento de alto volume se beneficiam do controle otimista para evitar o estrangulamento de conexões no banco de dados.
  • Falhas de concorrência geram exceções controladas que exigem políticas inteligentes de nova tentativa na camada de aplicação.
  • A escolha entre bloqueios pessimistas e otimistas depende diretamente da frequência de conflitos esperada para cada tipo de transação.

O Desafio da Concorrência em Sistemas Financeiros

Imagine que duas pessoas tentam usar o mesmo cartão de crédito para fazer compras simultâneas enquanto possuem apenas um saldo de cem reais na conta. Na engenharia de software, chamamos essa disputa pelo mesmo recurso de concorrência. Se o sistema bancário não for projetado com cuidado, ambas as transações podem ser aprovadas antes que o saldo seja atualizado, permitindo que o cliente gaste o dobro do que possui. Esse cenário catastrófico é o pesadelo de qualquer engenheiro de backend, a parte do sistema que roda escondida nos servidores e processa dados em segundo plano.

Para evitar que o dinheiro apareça ou desapareça do nada, os bancos de dados relacionais oferecem mecanismos para controlar o acesso simultâneo. Tradicionalmente, muitos sistemas usam o que chamamos de bloqueio pessimista, uma estratégia em que o sistema trava a linha da tabela assim que ela é lida, impedindo que qualquer outra operação mexa nela até que a primeira termine. Na prática, isso funciona como trancar a porta do banheiro por dentro: ninguém mais entra até você sair. O problema é que, em sistemas modernos com milhares de requisições por segundo, essa fila indesejada causa lentidão extrema e esgota as conexões disponíveis.

Como Funciona o Bloqueio Otimista na Prática

O bloqueio otimista nasce justamente para resolver o problema da lentidão gerada pelas travas excessivas. A ideia central é baseada na confiança: o sistema assume que a grande maioria das transações não vai brigar pelo mesmo saldo ao mesmo tempo. Na prática, quando um processo lê o saldo de uma conta para fazer um Pix ou uma transferência, ele anota um número de versão ou um carimbo de data e hora daquele registro. Esse número funciona como a edição de um documento compartilhada no Google Docs: você edita a sua versão e, ao salvar, o sistema verifica se mais alguém alterou o arquivo enquanto você trabalhava nele.

Quando chega a hora de salvar a alteração no banco de dados, a aplicação executa um comando SQL que verifica se a versão atual da linha ainda é exatamente aquela que foi lida minutos antes. Se ninguém mexeu no registro nesse meio tempo, a atualização acontece com sucesso e o número da versão é incrementado em um. Se outro processo alterou o saldo primeiro, o número da versão no banco não vai coincidir com o número que a nossa aplicação tem guardado. O banco de dados relacional percebe essa divergência e simplesmente recusa a alteração, garantindo que nenhum dado seja sobrescrito de maneira incorreta ou silenciosa.

Implementando Controle de Versão com Código SQL

Para visualizar essa dinâmica no código, imagine que nossa tabela de contas possui uma coluna chamada versao, além do saldo. Quando queremos debitar dinheiro, precisamos consultar o saldo atual e a versão correspondente para enviarmos na condição de atualização. Se a versão mudou, a quantidade de linhas afetadas pelo comando será zero, indicando que houve um conflito de concorrência entre as threads do servidor. Vejamos um exemplo prático utilizando uma transação típica em linguagem SQL:

-- Passo 1: Ler o saldo atual e a versão da conta do cliente
SELECT saldo, versao FROM contas WHERE id = 42;

-- Passo 2: Tentar atualizar aplicando o incremento de versão
UPDATE contas 
SET saldo = saldo - 150.00, versao = versao + 1 
ID = 42 AND versao = 3;

-- O driver da aplicação verifica se a linha foi afetada. 
-- Se linhas_afetadas == 0, houve concorrência e a transação deve ser repetida.

Esse padrão impede que atualizações perdidas aconteçam, pois a checagem da versão ocorre de forma atômica no momento em que o banco executa o comando de alteração. Na prática, a aplicação precisa estar preparada para capturar essa falha de atualização e decidir se vai tentar novamente o processo ou se vai retornar um erro amigável para o usuário final informando que o sistema está muito concorrido naquele momento.

Estratégias para Tratar Conflitos e Novas Tentativas

Quando o bloqueio otimista detecta um conflito, a primeira reação da aplicação não deve ser desistir da operação e frustrar o usuário. Como os conflitos em contas correntes costumam ser eventos pontuais e raros, a melhor abordagem é implementar uma lógica de repetição automática, conhecida no ecossistema de engenharia como retries. A rotina tenta executar a leitura e a escrita novamente após alguns milissegundos. Para evitar que centenas de requisições batam ao mesmo tempo gerando uma tempestade de novas tentativas, os engenheiros utilizam uma técnica chamada espera exponencial com alívio aleatório, onde o tempo de espera entre cada nova tentativa aumenta de forma progressiva e descentralizada.

No entanto, nem todo cenário financeiro aceita repetições infinitas. Imagine um sistema de leilão de curtíssimo prazo ou uma alta bolsa de valores onde milissegundos definem quem comprou um ativo. Se a taxa de conflitos em uma mesma linha disparar porque centenas de pessoas tentam comprar o último ingresso disponível para um show, o bloqueio otimista sofre com o que chamamos de starvation, ou seja, muitas tentativas falham repetidamente e o sistema perde desempenho. Nesses casos extremos de alta contenção, onde a chance de colisão deixa de ser rara e passa a ser garantida, o bloqueio pessimista ou filas assíncronas baseadas em eventos tornam-se escolhas arquiteturais muito mais seguras.

Considerações Finais sobre Consistência e Escalabilidade

A escolha entre bloqueios otimistas e pessimistas define a saúde operacional de uma aplicação financeira de grande escala. O bloqueio otimista brilha em cenários onde a maioria das operações ocorre em contas distintas e a concorrência direta sobre um mesmo registro é estatisticamente baixa. Ele elimina gargalos de rede e de processamento, permitindo que o banco de dados sirva milhares de requisições simultâneas sem manter conexões travadas em uma fila invisível. Dominar esse equilíbrio entre confiança nos dados e performance sistêmica é o que separa um software frágil de uma arquitetura financeira resiliente e pronta para o crescimento.