Pular para o conteudo principal

Transações na prática: commit, rollback e savepoint

Como pensar transação como proteção contra estado pela metade, o que commit e rollback realmente fazem e onde savepoint entra sem virar enfeite raro.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

O problema

Transação costuma ser ensinada de um jeito meio ritualístico.

  • abre transação
  • faz umas operações
  • commit no final

Só que isso não ajuda muito a entender o motivo.

O problema real é outro:

às vezes uma regra de negócio depende de mais de uma escrita, e o banco não pode parar no meio do caminho.

Exemplo clássico:

  • debitar saldo de uma conta
  • creditar saldo em outra
  • registrar o histórico

Se uma parte entra e outra falha, o dado fica pela metade.

E dado pela metade costuma ser pior do que erro explícito.

Porque depois ninguém sabe se precisa refazer, compensar ou investigar fraude.

Então transação não existe para “rodar várias queries juntas”.

Ela existe para proteger uma mudança que só faz sentido completa.

Modelo mental

Pense assim:

transação é uma janela curta em que o banco trata várias mudanças como um pacote só.

Enquanto esse pacote não fecha, você ainda não quer assumir que aquilo virou verdade definitiva.

Nesse contexto:

  • commit confirma o pacote
  • rollback joga fora o que aconteceu dentro dele
  • savepoint cria um ponto intermediário para voltar sem perder tudo

Essa é a leitura mais útil.

Não é mágica.

Não é “modo seguro”.

É um mecanismo para dizer:

  • ou essa mudança inteira vale
  • ou essa mudança inteira não vale

E isso vem com custo.

Porque, durante essa janela, o banco pode segurar lock, ocupar conexão e aumentar disputa com outras operações.

Transação boa protege a regra sem ficar aberta mais tempo do que precisa.

Quebrando o problema

Transação faz sentido quando a regra atravessa mais de uma escrita

Se uma única operação atômica já resolve, melhor.

Mas quando a regra depende de duas ou mais mudanças coerentes, transação costuma entrar.

Casos comuns:

  • criar pedido e reservar estoque
  • registrar pagamento e atualizar saldo
  • apagar registros relacionados em conjunto
  • gravar dado principal e seu histórico

O ponto não é a quantidade de queries.

O ponto é a dependência lógica entre elas.

Commit não é “acabou o código, então persiste”

Commit é o momento em que você aceita aquele novo estado como válido.

Isso parece detalhe de linguagem, mas muda o raciocínio.

Porque o ideal é chegar no commit só depois que:

  • as validações importantes passaram
  • as escritas necessárias aconteceram
  • a invariável da operação continua verdadeira

Se você trata commit como formalidade, começa a desenhar fluxo sem clareza sobre qual condição realmente fecha a operação.

Rollback protege contra estado parcial

Rollback não é só “deu exception”.

Ele existe para descartar uma mudança que deixou de ser válida antes do fechamento.

Pode ser erro técnico:

  • timeout
  • violação de constraint
  • deadlock

Ou erro de regra:

  • saldo insuficiente
  • estoque acabou
  • pedido já foi processado

O que importa é:

se a operação não pode terminar correta, melhor apagar o pacote inteiro do que deixar resto espalhado.

Savepoint é um checkpoint dentro da transação

Muita gente quase nunca usa savepoint e tudo bem.

Mas é útil entender para não parecer palavra decorada.

Savepoint é um ponto intermediário para o qual você pode voltar sem jogar fora toda a transação.

Isso ajuda quando existe uma parte opcional ou recuperável dentro do fluxo.

Você mantém o núcleo da operação e desfaz só o trecho problemático.

Não é recurso para qualquer caso.

É ferramenta de precisão.

Transação longa demais cobra caro

Outro erro comum é pensar só em corretude e esquecer custo operacional.

Quanto mais tempo a transação fica aberta, maior a chance de:

  • lock mais demorado
  • contenção
  • timeout
  • throughput pior
  • disputa com outras transações

Então a pergunta madura não é só “preciso de transação?”.

Também é:

  • por quanto tempo ela vai ficar aberta?
  • o que realmente precisa ficar dentro dela?

Chamada externa dentro da transação costuma ser má ideia

Esse ponto aparece muito em sistema real.

Exemplo ruim:

  1. abre transação
  2. grava pedido
  3. chama API de pagamento
  4. espera resposta
  5. commit

Enquanto você espera rede, o banco fica com a transação aberta sem necessidade.

Isso aumenta risco e contenção.

Em geral, o mais saudável é:

  • deixar dentro da transação só o que precisa de consistência local no banco
  • empurrar efeito externo para depois, ou usar padrão como outbox quando fizer sentido

Quando tudo vai para dentro da transação por medo, o sistema troca estado parcial por contenção desnecessária.

Transação não resolve sistema distribuído inteiro

Esse também é um tropeço clássico.

Transação do banco resolve bem o que está dentro daquele contexto transacional.

Ela não garante, sozinha:

  • consistência entre dois serviços
  • sincronismo com fila
  • sucesso de e-mail
  • efeito em sistema terceiro

Quando o fluxo cruza fronteiras, o problema deixa de ser só “abre transação”.

Exemplo simples

Imagine uma transferência entre duas contas.

Você precisa:

  1. debitar 100 da conta A
  2. creditar 100 na conta B
  3. registrar uma linha no histórico

Sem transação, pode acontecer algo assim:

  1. débito funciona
  2. crédito falha
  3. histórico nem chega a ser gravado

Resultado:

  • a conta A perdeu dinheiro
  • a conta B não recebeu
  • o sistema ainda ficou sem trilha clara do que aconteceu

Com transação, a ideia é:

begin;

update accounts
set balance = balance - 100
where id = 'A';

update accounts
set balance = balance + 100
where id = 'B';

insert into transfers (from_account, to_account, amount)
values ('A', 'B', 100);

commit;

Se alguma etapa crítica falhar antes do commit, você faz rollback e volta ao estado anterior.

Agora imagine que existe uma etapa opcional, como tentar gravar uma observação complementar ou um vínculo secundário que não deve derrubar a transferência inteira.

Você pode usar algo como:

begin;

-- núcleo da operação
update accounts ...
update accounts ...

savepoint optional_step;

-- passo opcional
insert into transfer_notes (...)

-- se esse trecho falhar:
rollback to savepoint optional_step;

commit;

O núcleo continua.

Só o trecho opcional volta.

Erros comuns

  • Usar transação em volta de qualquer fluxo sem primeiro nomear a regra que precisa ficar inteira.
  • Colocar chamada HTTP, fila ou processamento demorado dentro da transação.
  • Achar que commit no final resolve desenho ruim de concorrência.
  • Ignorar que transação longa aumenta lock e contenção.
  • Tratar savepoint como solução elegante para fluxo confuso demais.
  • Esquecer que, fora do banco local, talvez você precise de outbox, compensação ou outro arranjo.

Como um senior pensa

Um senior normalmente começa pela invariável.

Ele pergunta:

  • o que não pode ficar quebrado se essa operação falhar no meio?

Depois disso, ele decide o tamanho mínimo da transação.

Ou seja:

  • o que precisa entrar
  • o que precisa ficar fora
  • o que pode virar efeito posterior

Ele também evita linguagem mágica.

Em vez de falar “usa transação e pronto”, costuma falar algo como:

  • “o débito e o crédito precisam fechar juntos”
  • “o banco não pode observar esse estado parcial”
  • “a parte externa eu desacoplo para não segurar lock à toa”

E mais importante:

ele sabe que transação protege corretude local, mas não absolve desenho ruim.

O que o entrevistador quer ver

Em entrevista, ninguém está medindo se você decorou sintaxe de BEGIN e COMMIT.

O que costuma estar sendo avaliado é se você:

  • entende quando o problema é estado parcial
  • sabe explicar commit, rollback e savepoint em linguagem simples
  • reconhece custo de lock e contenção
  • evita colocar rede dentro da transação
  • não vende transação como solução universal para sistema distribuído

Uma resposta boa soa mais ou menos assim:

“Eu uso transação quando a regra depende de mais de uma escrita que precisa fechar junta. Commit confirma o pacote, rollback descarta se a operação deixou de ser válida e savepoint serve para voltar um trecho intermediário sem perder tudo. E eu tento manter a transação curta, sem chamada externa dentro dela, porque corretude sem contenção também importa.”

Isso mostra entendimento técnico e julgamento.

Transação não existe para juntar queries. Existe para impedir verdade quebrada no banco.

Commit fecha o pacote. Rollback apaga o pacote. Savepoint volta um pedaço sem fingir que tudo é igual.

Resumo rápido

O que vale manter na cabeça

Checklist de pratica

Use isto ao responder

Você concluiu este artigo

Próximo artigo Bug intermitente: por onde começar Artigo anterior Como decidir entre SQL e NoSQL

Continue explorando

Artigos relacionados