Pular para o conteudo principal

Bancos de Dados e Concorrência

Como pensar quando duas transações disputam o mesmo dado e por que corretude no banco não nasce só de fazer um select e depois um update.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

O problema

Muita conversa sobre concorrência começa na aplicação e termina cedo demais.

Mas vários bugs realmente perigosos aparecem no ponto em que duas execuções tentam decidir e gravar sobre o mesmo dado.

Exemplos clássicos:

  • vender a última unidade duas vezes
  • cobrar duas vezes a mesma intenção
  • deixar saldo negativo
  • sobrescrever um estado mais novo com uma decisão baseada em leitura velha

Quando isso acontece, o banco deixa de ser só armazenamento.

Ele vira parte central da corretude.

Modelo mental

Pense assim:

banco e concorrência é o problema de manter uma regra de negócio verdadeira mesmo quando duas transações tentam mudá-la ao mesmo tempo.

Isso ajuda porque tira a conversa de abstração solta.

O ponto não é “usar transação porque sim”.

O ponto é responder:

  • qual dado está sendo disputado?
  • qual invariável não pode quebrar?
  • qual proteção garante isso com custo aceitável?

Quebrando o problema

O erro comum é read, decide, write inocente

Esse fluxo aparece toda hora:

  1. ler valor atual
  2. decidir com base nele
  3. gravar novo valor

Sozinho, parece certo.

Sob concorrência, duas transações podem ler o mesmo estado antigo e tomar a mesma decisão como se fossem as únicas no mundo.

É aí que nasce muito bug sério.

Operação atômica resolve mais do que muita gente imagina

Às vezes você não precisa de uma grande coreografia transacional.

Precisa só transformar a intenção em uma escrita atômica.

Exemplos:

  • decrementar estoque apenas se ainda houver estoque
  • atualizar saldo só se a condição continuar válida
  • mudar status só se ele ainda estiver no estado esperado

Quando a regra cabe numa operação única e verificável, isso costuma ser mais simples e mais barato do que lockar meio sistema.

Transação ajuda quando a regra atravessa mais de um passo

Se a corretude depende de várias leituras e escritas coerentes entre si, a conversa sobe de nível.

Aí entram coisas como:

  • começar transação
  • garantir que a visão dos dados continue aceitável
  • persistir ou abortar em bloco

Só que “usar transação” ainda não fecha a discussão.

Porque o comportamento real depende também de isolamento, contenção e padrão de acesso.

Lock é defesa útil, mas cobra preço

Lock pode ser exatamente o que protege um recurso muito disputado.

Mas ele cobra:

  • menos paralelismo
  • mais espera
  • risco de contenção alta
  • possibilidade de deadlock se o desenho for ruim

Por isso lock não é prêmio de maturidade.

É ferramenta cara que vale quando a disputa justifica.

Isolamento não é detalhe acadêmico

Muita gente escuta “nível de isolamento” e desliga.

Mas a pergunta prática é simples:

que tipo de leitura ou interferência concorrente eu aceito entre duas transações?

Dependendo do caso, você pode tolerar alguma variação.

Em outros, não.

Fluxo financeiro, reserva e estoque costumam exigir mais cuidado do que consulta agregada para dashboard.

Banco não salva modelagem ruim sozinho

Outro erro comum é esperar que o banco compense uma lógica mal desenhada.

Se o fluxo:

  • lê demais antes de decidir
  • espalha decisão entre vários serviços
  • depende de uma janela longa entre ler, decidir e gravar

o banco pode ajudar, mas talvez o problema real seja modelagem do processo.

Às vezes a melhor resposta não é aumentar lock.

É reduzir disputa, serializar por chave ou mudar o fluxo.

Exemplo simples

Imagine saldo de conta em 100.

Duas transações querem debitar 80.

Fluxo ingênuo:

  1. transação A lê 100
  2. transação B lê 100
  3. as duas concluem que podem debitar
  4. ambas gravam novo valor

Dependendo de como isso foi feito, você pode ter:

  • saldo incorreto
  • update perdido
  • regra de negócio quebrada

Fluxo melhor depende do caso, mas pode ser algo como:

  • update atômico condicionado
  • lock na linha durante a decisão crítica
  • transação com verificação coerente até a escrita final

O ponto importante não é decorar qual SQL usar.

É entender que a regra “saldo não pode ficar inválido” precisa ser protegida no ponto da disputa.

Erros comuns

  • Fazer select e depois update como se ninguém mais pudesse ler o mesmo valor.
  • Tratar transação como selo mágico sem pensar em isolamento e contenção.
  • Colocar lock em excesso e derrubar throughput.
  • Ignorar número de linhas afetadas em operação condicional.
  • Resolver no frontend ou no código de aplicação algo que só fica realmente correto no armazenamento.

Como um senior pensa

Quem tem mais experiência normalmente pergunta:

onde exatamente a disputa acontece e qual a menor garantia que mantém essa regra verdadeira?

Essa pergunta é ótima porque evita dois extremos:

  • solução fraca demais
  • solução cara demais

Em banco, senioridade aparece muito nisso.

Não em falar nomes difíceis.

Mas em saber quando um update atômico basta, quando a transação precisa ser mais rígida e quando o desenho inteiro da operação merece mudar.

O que o entrevistador quer ver

Em entrevista, o avaliador quer ver se você entende concorrência como problema de corretude no dado.

Sinais bons:

  • você nomeia a invariável
  • reconhece read/decide/write perigoso
  • fala de operação atômica, transação e lock com critério
  • menciona custo de contenção e throughput

Uma resposta forte pode soar assim:

“Quando duas transações disputam o mesmo dado, eu primeiro nomeio a regra que não pode quebrar. Se couber em uma escrita condicional ou atômica, prefiro esse caminho. Se a decisão atravessa mais de um passo, avalio transação e isolamento. E só uso lock mais pesado quando a disputa realmente exige.”

Concorrência no banco não é detalhe de implementação. É onde muita regra de negócio vive ou morre.

Quando a proteção certa fica no lugar certo, a aplicação para de depender de timing favorável.

Resumo rápido

O que vale manter na cabeça

Checklist de pratica

Use isto ao responder

Você concluiu este artigo

Próximo artigo O que Acontece Quando Duas Pessoas Clicam ao Mesmo Tempo Artigo anterior Arquitetura Assíncrona: quando o seu await não é suficiente

Continue explorando

Artigos relacionados