Pular para o conteúdo
10 min de leitura

Transações e ACID: o que garante que seu dado não corrompe

Por Equipe Tech do Sonne ·

Atomicidade, consistência, isolamento e durabilidade explicados na prática, com exemplos de transferência bancária, commit, rollback e falhas reais.

Neste artigo

Imagine uma transferência bancária. O sistema precisa debitar cem reais de uma conta e creditar exatamente o mesmo valor em outra. São duas operações, mas do ponto de vista do negócio elas formam um único ato indivisível: ou as duas acontecem, ou nenhuma acontece. Se a máquina cair, a rede oscilar ou o processo morrer no exato instante entre o débito e o crédito, o dinheiro não pode simplesmente sumir. Esse cenário aparentemente simples é o problema fundamental que as transações de banco de dados resolvem.

Uma transação é um agrupamento de operações que o banco trata como uma unidade única. Você a abre, executa quantos comandos precisar, e a encerra com COMMIT — que torna tudo permanente — ou com ROLLBACK — que desfaz tudo como se nada tivesse acontecido. Entre esses dois pontos, o banco carrega a responsabilidade de manter garantias fortes sobre o que pode e o que não pode acontecer com os seus dados, mesmo diante de falhas de hardware, concorrência e erros de aplicação.

Essas garantias têm um nome coletivo: ACID, um acrônimo de atomicidade, consistência, isolamento e durabilidade. Cada letra descreve uma promessa diferente, e entender o que cada uma cobre — e o que não cobre — é a diferença entre confiar cegamente no banco e usá-lo com competência. Vamos percorrer as quatro com exemplos concretos.

Atomicidade: tudo ou nada#

A atomicidade garante que uma transação é indivisível: ou todas as suas operações têm efeito, ou nenhuma tem. Não existe meio-termo. Não existe "debitou mas não creditou".

Volte à transferência. Dentro de uma transação, o banco executa o UPDATE que subtrai da conta de origem e o UPDATE que soma na conta de destino. Se ambos rodam sem erro e você chama COMMIT, o resultado fica gravado. Mas se qualquer coisa falhar no meio — uma restrição violada, uma exceção na aplicação, uma queda de energia — o banco executa (ou recupera-se para) um ROLLBACK, e o débito que já tinha sido aplicado é desfeito. A conta de origem volta ao saldo anterior. É como se a transação nunca tivesse começado.

O que torna isso possível são estruturas internas como o log de transações (ou write-ahead log). Antes de alterar os dados de fato, o banco registra num log o que pretende fazer e o que era o estado anterior. Se o processo morre no meio, na próxima inicialização o banco lê esse log e decide: transações que chegaram ao COMMIT são reaplicadas se necessário; transações incompletas são desfeitas. O usuário nunca vê o estado quebrado do meio do caminho.

Para o desenvolvedor, a lição prática é: agrupe na mesma transação todas as escritas que precisam viver ou morrer juntas. Um erro comum é fazer o débito numa transação, dar COMMIT, e só depois abrir outra transação para o crédito. Entre as duas, existe uma janela em que o dinheiro sumiu. A atomicidade só protege o que está dentro dos mesmos limites transacionais.

Consistência: o banco nunca fica em estado inválido#

A consistência garante que uma transação leva o banco de um estado válido a outro estado válido, respeitando todas as regras declaradas. Chaves primárias únicas, chaves estrangeiras, restrições CHECK, campos NOT NULL — nenhuma dessas regras pode ser violada por uma transação que deu COMMIT.

Suponha uma restrição que diz que o saldo de uma conta nunca pode ser negativo, expressa como um CHECK (saldo >= 0). Se a transferência tentar debitar mais do que existe na conta de origem, o UPDATE violaria essa restrição. O banco não permite: ele recusa a operação, a transação falha e, por atomicidade, tudo é desfeito. O estado inválido — saldo negativo — nunca chega a existir de forma persistente.

É importante entender que a consistência do ACID trata das regras que o banco conhece. Ela garante que as restrições declaradas no schema serão respeitadas. Ela não garante, por si só, que a sua lógica de negócio esteja correta: se você esqueceu de declarar a restrição de saldo, o banco alegremente aceitará o saldo negativo, porque para ele aquilo é um estado válido. Consistência, nesse sentido, é uma parceria: o banco cumpre as regras que você declarou, então declare as regras que importam. Quanto mais invariantes de negócio você conseguir expressar como restrições no schema, mais o banco trabalha a seu favor.

Isolamento: transações concorrentes não se atrapalham#

O isolamento garante que transações executando ao mesmo tempo produzam o mesmo resultado que produziriam se rodassem uma após a outra. Do ponto de vista de cada transação, é como se ela estivesse sozinha no banco.

Esse é o pilar mais sutil, porque o mundo real tem centenas de transações rodando em paralelo. Imagine dois caixas eletrônicos sacando da mesma conta ao mesmo tempo. A conta tem cem reais. Cada caixa lê o saldo, vê cem, e cada um autoriza um saque de oitenta. Sem isolamento, os dois leem cem, os dois debitam oitenta, e a conta termina com saldo negativo — foram sacados cento e sessenta de uma conta de cem. O dinheiro foi criado do nada.

O isolamento impede essa classe de erro fazendo com que as transações não enxerguem os estados intermediários umas das outras. Os mecanismos variam — travas (locks) que serializam o acesso a uma linha, ou versionamento que dá a cada transação uma foto coerente dos dados (MVCC) — mas o objetivo é o mesmo: o resultado final deve ser equivalente a alguma ordem sequencial das transações.

Na prática, o isolamento é regulável. Bancos oferecem níveis de isolamento — como READ COMMITTED, REPEATABLE READ e SERIALIZABLE — que trocam garantia por desempenho. Níveis mais fracos permitem mais concorrência ao custo de deixar passar certas anomalias, como leituras que mudam de valor dentro da mesma transação. Níveis mais fortes eliminam essas anomalias, mas travam mais e aumentam a chance de conflitos que forçam uma transação a ser reiniciada. Escolher o nível certo é um tema em si, mas o ponto aqui é que o isolamento não é ligado-ou-desligado: é um botão que você ajusta conforme a criticidade da operação.

Por que o isolamento perfeito é caro#

Isolamento total — SERIALIZABLE de verdade — significa que o banco se comporta como se só uma transação rodasse por vez. Isso é a garantia mais forte e a mais lenta, porque reduz o paralelismo. Por isso a maioria dos bancos usa por padrão um nível intermediário, que evita as anomalias mais perigosas sem pagar o preço total. Entender esse trade-off evita tanto o erro de rodar tudo em SERIALIZABLE e criar gargalos, quanto o erro oposto de rodar tudo no nível mais fraco e ser surpreendido por corrupção lógica sob concorrência.

Durabilidade: uma vez confirmado, é para sempre#

A durabilidade garante que, uma vez que o banco confirmou um COMMIT, os dados sobrevivem a qualquer falha subsequente — inclusive queda de energia. Se o banco disse "confirmado", aquele dado está gravado em armazenamento permanente e voltará intacto mesmo que o servidor seja desligado no instante seguinte.

O mecanismo central, de novo, é o write-ahead log. Antes de responder "commit efetuado" para a aplicação, o banco garante que o registro daquela transação foi fisicamente gravado em disco — não apenas em memória volátil, mas em mídia que sobrevive a um corte de energia. Só então a confirmação volta ao cliente. Se a máquina cair logo depois, a recuperação relê o log e reconstrói o estado confirmado.

Há uma implicação prática importante aqui. A durabilidade cobra seu preço em latência: esperar a gravação física em disco é mais lento do que apenas alterar a memória. Alguns sistemas oferecem modos de commit assíncrono, em que o banco responde "ok" antes de a gravação estar plenamente garantida, ganhando velocidade ao custo de uma pequena janela em que um crash poderia perder as últimas transações. Isso pode ser aceitável para dados de baixa criticidade e é veneno para dados financeiros. Saber que essa configuração existe evita tanto sacrificar durabilidade sem perceber quanto pagar por durabilidade máxima onde ela não é necessária.

Vale lembrar também que a durabilidade do banco em uma máquina não substitui backups e replicação. O log garante que um COMMIT sobrevive a um crash daquele servidor; ele não protege contra o disco físico morrer, contra um DELETE acidental ou contra um datacenter pegar fogo. Durabilidade é uma garantia sobre falhas de processo e energia, não um plano de continuidade completo.

Colocando ACID para trabalhar no código#

Entender ACID muda como você escreve código de aplicação. O padrão correto é abrir a transação, executar as operações relacionadas, e fechar com COMMIT em caso de sucesso ou ROLLBACK em caso de erro — sempre garantindo, com um bloco de tratamento adequado, que uma exceção no meio do caminho leve ao ROLLBACK e nunca deixe uma transação pendurada.

Um erro frequente é manter transações abertas por tempo demais. Enquanto uma transação está aberta, ela pode estar segurando travas que bloqueiam outras transações, e está consumindo recursos de versionamento. Fazer uma chamada de rede lenta ou esperar input do usuário no meio de uma transação aberta é receita para contenção e travas. A regra é: abra a transação o mais tarde possível, faça o trabalho de banco rapidamente, e feche o mais cedo possível. Trabalho que não é de banco fica fora dos limites transacionais.

Outro ponto é a idempotência e o retry. Como transações podem falhar por conflito de isolamento e precisar ser reexecutadas, o código que fala com o banco deve estar preparado para tentar de novo uma transação que abortou por essa razão. Isso significa estruturar a operação de modo que reexecutá-la produza o mesmo resultado correto, sem duplicar efeitos.

Por fim, respeite os limites do que a transação cobre. Uma transação de banco garante ACID dentro daquele banco. Ela não estende essas garantias para uma chamada a um serviço externo, para o envio de um e-mail ou para uma gravação em outro sistema. Coordenar consistência entre sistemas diferentes é um problema distinto, resolvido por padrões como outbox transacional, em que a intenção de notificar o mundo externo é gravada na mesma transação do dado, e a notificação efetiva acontece depois, de forma confiável.

Fechando o raciocínio#

ACID não é jargão para impressionar; é o contrato que permite construir sistemas sobre os quais se pode confiar dinheiro, estoque e identidade. A atomicidade garante o tudo-ou-nada. A consistência garante que as regras declaradas serão respeitadas. O isolamento garante que a concorrência não corrompa a lógica. A durabilidade garante que o confirmado sobrevive à catástrofe.

Cada garantia tem um custo, e um bom engenheiro sabe quando pagar por ela integralmente e quando afrouxar com consciência. O que nunca é aceitável é ignorar as garantias por desconhecimento e descobrir, num incidente de produção, que o dinheiro sumiu porque o débito e o crédito viviam em transações separadas. Transações bem usadas são a diferença entre um banco de dados que protege seus dados e um que apenas os armazena até o primeiro imprevisto.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly