DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

Transactions & ACID

ACID

Toda vez que você faz uma transferência bancária, finaliza uma compra online ou reserva um assento num voo, um banco de dados está tomando decisões em frações de segundo que determinam se aquela operação aconteceu de verdade, ou não aconteceu de jeito nenhum. O mecanismo que garante isso tem nome: ACID.

O conceito foi formalizado pelo cientista Jim Gray no final dos anos 1970, numa época em que bancos de dados começavam a ser usados em sistemas críticos e os engenheiros precisavam de um vocabulário preciso para falar sobre confiabilidade. Décadas depois, as quatro propriedades que ele descreveu continuam sendo o padrão pelo qual julgamos a solidez de qualquer banco de dados transacional.

Atomicidade

Uma transação é atômica quando ela é tudo ou nada. Não existe estado intermediário.

Imagine uma transferência de R$500 entre duas contas. A operação envolve dois passos: debitar da conta A e creditar na conta B. Se o sistema cair exatamente entre esses dois passos, o que acontece? Sem atomicidade, o dinheiro some — saiu de A, mas nunca chegou em B. Com atomicidade, o banco de dados garante que ou os dois passos acontecem juntos, ou nenhum deles é confirmado. A transação incompleta é desfeita automaticamente.

Consistência

Consistência garante que uma transação sempre leva o banco de dados de um estado válido para outro estado válido, respeitando todas as regras definidas no esquema — chaves estrangeiras, restrições de unicidade, campos obrigatórios.

Se uma regra diz que o saldo de uma conta não pode ser negativo, nenhuma transação pode violar isso. Mesmo que a transação seja atomicamente executada, ela será rejeitada se o resultado final quebrar uma restrição. O banco de dados nunca fica num estado que contradiz suas próprias regras.

Isolamento

Bancos de dados reais lidam com milhares de transações simultâneas. O isolamento define o quanto uma transação em andamento é visível para as outras.

Sem isolamento, dois usuários comprando o último ingresso disponível ao mesmo tempo poderiam ambos ver o ingresso como disponível, ambos confirmar a compra, e o sistema acabar com um ingresso vendido duas vezes. Com isolamento, as transações se comportam como se estivessem sendo executadas em série, uma após a outra — mesmo que na prática estejam rodando em paralelo.

Na prática, isolamento total tem um custo de desempenho alto, então os SGBDRs oferecem níveis de isolamento configuráveis — do mais permissivo (Read Uncommitted) ao mais restritivo (Serializable) — permitindo que cada sistema escolha o equilíbrio certo entre segurança e velocidade.

Durabilidade

Uma vez que o banco confirma uma transação — retornando um "commit" — aquele dado persiste, independente do que aconteça a seguir. Queda de energia, falha de hardware, reinicialização do servidor: nada desfaz uma transação já confirmada.

Isso é garantido por mecanismos como o write-ahead log (WAL): antes de qualquer dado ser modificado no disco, a intenção da modificação é registrada num log separado. Se o sistema cair no meio do processo, o banco consegue se recuperar relendo esse log e replicando as operações que ainda não haviam sido concluídas.

Por o ACID que importa?

ACID não é um detalhe de implementação, é uma promessa. Quando um banco de dados diz que é ACID-compliant, ele está dizendo que você pode confiar nele para operações onde o custo de um erro é alto.

É por isso que sistemas financeiros, hospitalares e de e-commerce continuam usando SGBDRs mesmo quando a escala poderia justificar uma alternativa. E é também por isso que, quando sistemas NoSQL precisam garantir transações confiáveis, eles acabam implementando suas próprias versões dessas propriedades — como o MongoDB fez ao adicionar transações multi-documento em 2018, décadas depois de o modelo relacional já oferecê-las por padrão.

Transactions

Aprender o ACID é fundamental para entender os conceitos de BEGIN, COMMIT, ROLLBACK, SAVEPOINT. Vou supor que o você realizou a leitura anterior. Cada um desses comandos acima existem para entregar as 4 garantias do ACID na prática.

Letra Significa O que garante
Atomicity Atomicidade Tudo ou nada — a transferência bancária inteira acontece, ou nenhuma parte dela acontece
Consistency Consistência O banco nunca fica num estado que viole regras (ex: saldo negativo se houver constraint impedindo)
Isolation Isolamento Transações simultâneas não "veem" os passos intermediários umas das outras
Durability Durabilidade Uma vez confirmada (COMMIT), a mudança sobrevive mesmo a uma queda de energia logo em seguid

Antes de entrar na sintaxe, entenda o porquê. Imagine uma transferência bancária: tirar R$100 da conta A e colocar na conta B. Isso são duas operações:

UPDATE contas SET saldo = saldo - 100 WHERE id = 'A';
UPDATE contas SET saldo = saldo + 100 WHERE id = 'B';
Enter fullscreen mode Exit fullscreen mode

Se o banco de dados executar a primeira linha, e exatamente nesse momento o servidor cair (falta de energia, crash, rede caiu), o dinheiro desaparece: saiu da conta A, mas nunca chegou na conta B. Isso é inaceitável.

Uma transação agrupa múltiplas operações em uma unidade atômica: ou todas acontecem, ou nenhuma acontece. Não existe meio-termo. É o mecanismo que garante que o cenário acima nunca aconteça.

BEGIN

Iniciando uma transação.

Sintaxe

BEGIN;
-- ou, dependendo do banco:
BEGIN TRANSACTION;
START TRANSACTION;
Enter fullscreen mode Exit fullscreen mode
Banco Sintaxe preferida
PostgreSQL BEGIN;
MySQL START TRANSACTION; (BEGIN também funciona)
SQL Server BEGIN TRANSACTION; (ou BEGIN TRAN)
Oracle Não precisa de BEGIN explícito — toda instrução DML já inicia uma transação implicitamente

O que acontece de fato

BEGIN marca o ponto a partir do qual todas as mudanças ficam "provisórias" — visíveis só dentro dessa mesma conexão/sessão, até que você dê COMMIT (confirmar) ou ROLLBACK (desfazer).

BEGIN;

UPDATE contas SET saldo = saldo - 100 WHERE id = 'A';
UPDATE contas SET saldo = saldo + 100 WHERE id = 'B';

-- Neste ponto, se você abrir outra conexão e consultar a tabela "contas",
-- ainda vai ver os saldos ANTIGOS (a menos que o nível de isolamento seja diferente do padrão)

COMMIT;
-- SÓ AGORA a mudança se torna permanente e visível para todo mundo
Enter fullscreen mode Exit fullscreen mode

Autocommit, o modo “padrão” que você não percebe

Por padrão, a maioria dos bancos roda em modo autocommit: cada instrução SQL individual já é sua própria transação implícita, com COMMIT automático logo depois. É por isso que um UPDATE isolado "simplesmente funciona" sem você nunca ter digitado BEGIN/COMMIT.

BEGIN explícito serve exatamente para desligar esse comportamento automático e agrupar várias instruções manualmente em uma única unidade atômica.

COMMIT

Confirmar a transação

Sintaxe

COMMIT;
Enter fullscreen mode Exit fullscreen mode

O que acontece de fato

COMMIT torna permanentes todas as mudanças feitas desde o BEGIN. A partir desse momento:

  • As mudanças ficam visíveis para todas as outras conexões/usuários.
  • As mudanças são duráveis — sobrevivem a uma queda de energia, restart do servidor, etc. (a letra D do ACID).
  • Não há mais como desfazer via ROLLBACK — se você errou, precisa fazer uma nova transação para corrigir (ex: um novo UPDATE revertendo o valor).
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 'A';
UPDATE contas SET saldo = saldo + 100 WHERE id = 'B';
COMMIT;-- agora sim, a transferência está "selada"
Enter fullscreen mode Exit fullscreen mode

Ponto-chave: enquanto não há COMMIT, a transação pode ser desfeita a qualquer momento. Depois do COMMIT, é definitivo.

ROLLBACK

O padrão mais comum no código de uma aplicação (pseudo-código):

BEGIN TRANSACTION
TRY:
  UPDATE contas SET saldo = saldo - 100 WHERE id = 'A'
  UPDATE contas SET saldo = saldo + 100 WHERE id = 'B'
  COMMIT
CATCH (erro):
  ROLLBACK   -- qualquer erro no meio do caminho desfaz tudo
Enter fullscreen mode Exit fullscreen mode

Isso garante que, se a segunda instrução falhar por qualquer motivo (conexão caiu, violação de constraint, timeout), a primeira instrução também é desfeita — nunca fica um saldo debitado sem o correspondente crédito.

Rollback automático em caso de erro grave

Em muitos bancos (Postgres é rigoroso nisso), se uma instrução dentro da transação falhar com erro, a transação inteira entra em estado de "abortada" e qualquer comando subsequente é rejeitado até você mandar ROLLBACK explicitamente — o banco não deixa você simplesmente ignorar o erro e continuar.

SAVEPOINT — Pontos de Rollback Parcial

ROLLBACK sozinho desfaz a transação inteira. Mas e se você tem uma transação longa, com vários passos, e só quer desfazer uma parte dela, mantendo o resto?

Sintaxe

BEGIN;

INSERT INTO pedidos (cliente_id, total)VALUES (1,500);
SAVEPOINT depois_do_pedido;

INSERT INTO itens_pedido (pedido_id, produto_id, quantidade)VALUES (1,99,3);
-- Ops, produto 99 não existe ou regra de negócio falhou

ROLLBACK TO SAVEPOINT depois_do_pedido;
-- Desfaz APENAS o insert de itens_pedido
-- O insert do pedido continua "de pé", esperando dentro da transação

INSERT INTO itens_pedido (pedido_id, produto_id, quantidade)VALUES (1,42,3);
-- Tenta de novo, com dado correto

COMMIT;-- confirma tudo: o pedido + o item correto
Enter fullscreen mode Exit fullscreen mode

Múltiplos savepoints

Você pode ter vários savepoints numa mesma transação, como "checkpoints" em sequência:

BEGIN;
  INSERT INTO log (mensagem)VALUES ('passo 1');
  SAVEPOINT sp1;

  INSERT INTO log (mensagem)VALUES ('passo 2');
  SAVEPOINT sp2;

  INSERT INTO log (mensagem)VALUES ('passo 3');

  ROLLBACK TO SAVEPOINT sp1;-- desfaz "passo 2" E "passo 3", mantém "passo 1"
COMMIT;
Enter fullscreen mode Exit fullscreen mode

Liberando um savepoint sem fazer rollback

RELEASE SAVEPOINT depois_do_pedido;
Enter fullscreen mode Exit fullscreen mode

Isso remove o savepoint (você não pode mais voltar para ele), mas não desfaz nada — as mudanças continuam lá, esperando o COMMIT final.

Uso real: processamento em lote com itens que podem falhar individualmente

BEGIN;
-- Processar 100 pedidos de uma importação, um por um
-- Para cada pedido:
SAVEPOINT antes_do_item;
  INSERT INTO pedidos ...;
  -- se der erro de validação específico desse pedido:
  ROLLBACK TO SAVEPOINT antes_do_item;
  -- continua para o próximo pedido, sem precisar desfazer os 99 anteriores
COMMIT;-- no final, confirma todos os que passaram
Enter fullscreen mode Exit fullscreen mode

⚠️ Nota de compatibilidade: MySQL (InnoDB) e PostgreSQL suportam SAVEPOINT bem. SQL Server também suporta, mas com sintaxe ligeiramente diferente: SAVE TRANSACTION nome_savepoint em vez de SAVEPOINT nome.

Top comments (0)