Entenda como as garantias ACID influenciam o design de bancos de dados e o comportamento da aplicação. Explore atomicidade, consistência, isolamento, durabilidade, trade-offs e exemplos práticos.

Quando você paga por mantimentos, reserva um voo ou transfere dinheiro entre contas, espera um resultado sem ambiguidade: ou deu certo, ou não. Bancos de dados tentam oferecer essa mesma certeza — mesmo quando muitas pessoas usam o sistema ao mesmo tempo, servidores caem ou redes falham.
Uma transação é uma unidade de trabalho que o banco trata como um único “pacote”. Pode incluir vários passos — subtrair inventário, criar um registro de pedido, cobrar um cartão e escrever um recibo — mas deve se comportar como uma ação coerente.
Se qualquer etapa falha, o sistema deve rebobinar até um ponto seguro em vez de deixar um meio-acabado bagunçado.
Atualizações parciais não são apenas falhas técnicas; viram chamados de suporte e risco financeiro. Por exemplo:
Essas falhas são difíceis de depurar porque tudo parece “mais ou menos correto”, mas os números não batem.
ACID é uma abreviação para quatro garantias que muitos bancos de dados podem fornecer para transações:
Não é uma marca específica nem um único recurso que você liga; é uma promessa sobre o comportamento.
Garantias mais fortes geralmente significam que o banco precisa fazer mais trabalho: coordenação extra, aguardar locks, rastrear versões e escrever em logs. Isso pode reduzir throughput ou aumentar latência sob carga pesada. O objetivo não é “máxima ACID o tempo todo”, e sim escolher garantias que combinem com os riscos reais do seu negócio.
Atomicidade significa que uma transação é tratada como uma única unidade de trabalho: ou ela termina completamente ou não tem efeito nenhum. Você nunca vê uma “meia atualização” no banco.
Imagine transferir $50 de Alice para Bob. Por trás dos panos, isso normalmente envolve pelo menos duas mudanças:
Com atomicidade, essas duas mudanças acontecem junto ou falham junto. Se o sistema não puder fazer ambas com segurança, ele não faz nenhuma. Isso evita o pesadelo onde Alice é cobrada mas Bob não recebe o dinheiro (ou Bob recebe sem Alice ser cobrada).
Bancos dão duas saídas a uma transação:
Um modelo mental útil é “rascunho vs publicar”. Enquanto a transação está em execução, as mudanças são provisórias. Apenas um commit as publica.
Atomicidade importa porque falhas são normais:
Se qualquer um desses acontecer antes do commit, a atomicidade garante que o banco pode fazer rollback para que trabalho parcial não vaze para saldos reais.
Atomicidade protege o estado do banco, mas sua aplicação ainda precisa lidar com incertezas — especialmente quando uma queda de rede deixa em dúvida se um commit aconteceu.
Dois complementos práticos:
Juntas, transações atômicas e retries idempotentes ajudam a evitar tanto atualizações parciais quanto cobranças duplicadas acidentais.
Consistência em ACID não significa “os dados parecem razoáveis” ou “todas as réplicas batem”. Significa que toda transação deve levar o banco de dados de um estado válido para outro estado válido — conforme as regras que você definiu.
Um banco de dados só pode manter consistência em relação a constraints explícitas, triggers e invariantes que descrevem o que “válido” significa para seu sistema. ACID não inventa essas regras; ele as aplica durante as transações.
Exemplos comuns incluem:
order.customer_id deve apontar para um cliente existente.Se essas regras existirem, o banco rejeitará qualquer transação que as viole — assim você não acaba com dados “meio válidos”.
Validação no app é importante, mas não é suficiente sozinha.
Um modo clássico de falha é checar algo no app (“email disponível”) e então inserir a linha. Sob concorrência, duas requisições podem passar na checagem ao mesmo tempo. Uma unique constraint no banco é o que garante que apenas uma inserção terá sucesso.
Se você codifica “saldo não negativo” como uma constraint (ou faz cumprir isso de forma confiável dentro de uma transação), então qualquer transferência que fosse deixar a conta negativa deve falhar como um todo. Se você não codificar essa regra em lugar nenhum, ACID não pode protegê-la — porque não há nada para aplicar.
Consistência é, em última análise, sobre ser explícito: defina as regras e deixe as transações garantir que elas nunca sejam quebradas.
Isolamento garante que transações não se atrapalhem. Enquanto uma transação está em andamento, outras não devem ver trabalho meio feito nem sobrescrever acidentalmente. O objetivo é simples: cada transação deve se comportar como se estivesse rodando sozinha, mesmo com muitos usuários ativos ao mesmo tempo.
Sistemas reais são ocupados: clientes fazem pedidos, agentes de suporte atualizam perfis, jobs em background reconciliam pagamentos — tudo ao mesmo tempo. Essas ações se sobrepõem no tempo e frequentemente tocam as mesmas linhas (um saldo, contagem de inventário ou vaga de reserva).
Sem isolamento, o tempo vira parte da lógica de negócio. Uma atualização de “subtrair estoque” pode competir com outro checkout, ou um relatório pode ler dados no meio de uma mudança e mostrar números que nunca existiram em um estado estável.
O “agir como se você estivesse sozinho” completo pode ser caro. Pode reduzir throughput, aumentar esperas (locks) ou causar retries de transação. Ao mesmo tempo, muitos fluxos não precisam da proteção mais estrita — ler analytics de ontem, por exemplo, tolera pequenas inconsistências.
Por isso bancos oferecem níveis de isolamento configuráveis: você escolhe quanta risco de concorrência aceita em troca de melhor desempenho e menos conflitos.
Quando o isolamento é fraco demais para sua carga, você encontrará anomalias clássicas:
Entender esses modos de falha facilita escolher um nível de isolamento que corresponda às promessas do seu produto.
Isolamento determina o que outras transações podem “ver” enquanto a sua ainda roda. Quando o isolamento é fraco para uma carga, aparecem anomalias — comportamentos tecnicamente possíveis, mas surpreendentes para usuários.
Dirty read acontece quando você lê dados que outra transação escreveu mas não confirmou.
Cenário: Alex transfere $500 para fora de uma conta, o saldo fica temporariamente $200, e você lê $200 antes da transferência de Alex falhar e fazer rollback.
Resultado para o usuário: cliente vê saldo incorreto baixo, uma regra antifraude dispara incorretamente, ou um atendente dá a resposta errada.
Non-repeatable read significa que você lê a mesma linha duas vezes e obtém valores diferentes porque outra transação confirmou alterações entre as leituras.
Cenário: você carrega o total de um pedido ($49,00) e, ao atualizar, vê $54,00 porque uma linha de desconto foi removida.
Resultado para o usuário: “meu total mudou enquanto eu estava finalizando”, levando a desconfiança ou abandono do carrinho.
Phantom read é parecido com non-repeatable read, mas com um conjunto de linhas: uma segunda consulta retorna linhas extras (ou faltantes) porque outra transação inseriu/excluiu registros que batem a condição.
Cenário: busca de hotel mostra “3 quartos disponíveis”, então ao tentar reservar o sistema rechecaa e encontra nenhum porque novas reservas foram adicionadas.
Resultado para o usuário: tentativas de dupla reserva, telas de disponibilidade inconsistentes ou venda em excesso.
Lost update ocorre quando duas transações leem o mesmo valor e ambas gravam atualizações, com a gravação posterior sobrescrevendo a anterior.
Cenário: dois admins editam o preço do mesmo produto. Ambos partem de $10; um salva $12, o outro salva $11 por último.
Resultado para o usuário: a mudança de alguém desaparece; totais e relatórios ficam errados.
Write skew acontece quando duas transações fazem mudanças que, individualmente, são válidas, mas juntas violam uma regra.
Cenário: Regra: “Pelo menos um médico de plantão deve estar escalado.” Dois médicos, independentemente, se desmarcam após verificar que o outro ainda está de plantão.
Resultado para o usuário: você fica sem cobertura, apesar de cada transação ter “passado” suas checagens.
Isolamento mais forte reduz anomalias, mas pode aumentar esperas, retries e custos sob alta concorrência. Muitos sistemas escolhem isolamento mais fraco para leituras analíticas, enquanto usam configurações mais rígidas para movimentação de dinheiro, reservas e outros fluxos críticos para corretude.
Isolamento trata do que sua transação pode “ver” enquanto outras ocorrem. Bancos expõem isso como níveis de isolamento: níveis mais altos reduzem comportamentos surpreendentes, mas podem custar throughput ou aumentar esperas.
Times frequentemente escolhem Read Committed como padrão para apps de usuário: bom desempenho, e “sem dirty reads” corresponde à maioria das expectativas.
Use Repeatable Read quando precisar de resultados estáveis dentro de uma transação (por exemplo, gerar uma fatura a partir de linhas de item) e puder tolerar alguma sobrecarga.
Use Serializable quando a corretude for mais importante que a concorrência (por exemplo, fazer cumprir invariantes complexas como “nunca vender mais do que o estoque”), ou quando você não consegue raciocinar facilmente sobre condições de corrida no código da aplicação.
Read Uncommitted é raro em sistemas OLTP; às vezes é usado para monitoramento ou relatórios aproximados onde leituras erradas ocasionais são aceitáveis.
Os nomes são padronizados, mas garantias exatas diferem por motor de banco de dados (e às vezes por configuração). Confirme na documentação do seu banco e teste as anomalias que importam para o seu negócio.
Durabilidade significa que, uma vez que uma transação é confirmada, seus resultados devem sobreviver a um crash — perda de energia, reinício de processo ou reboot de máquina. Se sua aplicação diz a um cliente “pagamento bem-sucedido”, durabilidade é a promessa de que o banco não vai “esquecer” esse fato após a próxima falha.
A maioria dos bancos relacionais alcança durabilidade com write-ahead logging (WAL). Em alto nível, o banco grava um “recibo” sequencial de mudanças no log no disco antes de considerar a transação como confirmada. Se ocorrer um crash, ele pode reproduzir o log na inicialização para restaurar as mudanças confirmadas.
Para manter o tempo de recuperação razoável, os bancos também criam checkpoints. Um checkpoint é um momento em que o banco garante que mudanças recentes suficientes foram gravadas nos arquivos de dados principais, para que a recuperação não precise reproduzir um histórico de log ilimitado.
Durabilidade não é um interruptor único; depende de quão agressivamente o banco força dados a armazenamento estável.
fsync) antes de confirmar o commit. Isso é mais seguro, mas pode aumentar latência.O hardware subjacente também importa: SSDs, controladoras RAID com cache de escrita e volumes de nuvem podem se comportar diferente sob falha.
Backups e replicação ajudam a recuperar ou reduzir tempo de inatividade, mas não são o mesmo que durabilidade. Uma transação pode ser durável no primário mesmo que não tenha chegado a uma réplica ainda, e backups são tipicamente snapshots no tempo, não garantias commit-a-commit.
Quando você BEGIN uma transação e depois COMMIT, o banco coordena muitas peças móveis: quem pode ler quais linhas, quem pode atualizá-las e o que acontece se duas pessoas tentarem mudar o mesmo registro ao mesmo tempo.
Uma escolha “por baixo” importante é como lidar com conflitos:
Muitos sistemas misturam as duas ideias dependendo da carga e do nível de isolamento.
Bancos modernos frequentemente usam MVCC (Multi-Version Concurrency Control): em vez de manter apenas uma cópia de uma linha, o banco mantém múltiplas versões.
Isso é uma grande razão pela qual alguns bancos lidam com muitas leituras e escritas concorrentes com menos bloqueio — embora conflitos de escrita ainda precisem ser resolvidos.
Locks podem levar a deadlocks: Transação A espera por um lock de B, enquanto B espera por um lock de A.
Bancos normalmente resolvem isso detectando o ciclo e abortando uma transação (uma “vítima de deadlock”), retornando um erro para que a aplicação possa tentar novamente.
Se a aplicação de ACID estiver criando atrito, você frequentemente verá:
Esses sintomas geralmente significam que é hora de revisar o tamanho das transações, indexação ou qual estratégia de isolamento/bloqueio se ajusta à carga.
Garantias ACID não são só teoria de banco; influenciam como você projeta APIs, jobs em background e até fluxos de UI. A ideia central é simples: decida quais passos precisam suceder juntos e então envolva apenas esses passos numa transação.
Uma API transacional boa geralmente mapeia para uma única ação de negócio, mesmo que toque várias tabelas. Por exemplo, uma operação /checkout pode: criar um pedido, reservar inventário e registrar uma intenção de pagamento. Essas gravações no banco normalmente devem viver numa única transação para que confirmem juntas (ou revertam juntas) se alguma validação falhar.
Um padrão comum é:
Isso mantém atomicidade e consistência enquanto evita transações lentas e frágeis.
Onde você coloca os limites de transação depende do que “uma unidade de trabalho” significa:
ACID ajuda, mas sua aplicação ainda deve lidar com falhas corretamente:
Evite transações longas, chamar APIs externas dentro de uma transação e tempo de pensamento do usuário dentro de uma transação (por exemplo, “bloquear a linha do carrinho e pedir usuário confirmar”). Isso aumenta contenção e torna conflitos de isolamento muito mais prováveis.
Se você está construindo um sistema transacional rapidamente, o maior risco raramente é “não conhecer ACID” — é espalhar inadvertidamente uma ação de negócio por múltiplos endpoints, jobs ou tabelas sem uma fronteira transacional clara.
Plataformas como Koder.ai podem ajudar a acelerar sem perder o desenho: você descreve um fluxo (por exemplo, “checkout com reserva de inventário e intenção de pagamento”) em um chat de planejamento, gera uma UI React mais backend em Go + PostgreSQL e itera com snapshots/rollback se um schema ou borda de transação precisar mudar. O banco ainda aplica as garantias; o valor está em acelerar o caminho de um design correto para uma implementação funcionando.
Um único banco normalmente entrega garantias ACID dentro de uma fronteira transacional. Quando você espalha trabalho por múltiplos serviços (e frequentemente múltiplos bancos), manter essas mesmas garantias fica mais difícil — e mais caro quando tentam implementá-las.
Consistência estrita significa que toda leitura vê a “verdade última” confirmada. Alta disponibilidade significa que o sistema continua respondendo mesmo quando partes estão lentas ou inacessíveis.
Num setup multi-serviço, um problema de rede temporário pode forçar uma escolha: bloquear ou falhar requisições até que todos os participantes concordem (mais consistente, menos disponível), ou aceitar que serviços possam ficar brevemente dessincronizados (mais disponível, menos consistente). Nenhuma opção é “sempre certa” — depende dos erros que seu negócio tolera.
Transações distribuídas exigem coordenação entre fronteiras que você não controla totalmente: atrasos de rede, retries, timeouts, crashes de serviço e falhas parciais.
Mesmo que cada serviço esteja correto, a rede pode criar ambiguidade: o serviço de pagamento confirmou mas o de pedidos nunca recebeu a confirmação. Para resolver isso com segurança, sistemas usam protocolos de coordenação (como two-phase commit), que podem ser lentos, reduzir disponibilidade em falhas e aumentar complexidade operacional.
Sagas quebram um fluxo em passos, cada um confirmado localmente. Se um passo posterior falha, passos anteriores são “desfeitos” por ações compensatórias (por exemplo, reembolsar uma cobrança).
Outbox/inbox tornam a publicação e consumo de eventos confiáveis. Um serviço grava dados de negócio e um registro de “evento a publicar” na mesma transação local (outbox). Consumidores registram IDs de mensagem processados (inbox) para lidar com retries sem duplicar efeitos.
Consistência eventual aceita janelas curtas onde dados diferem entre serviços, com um plano claro de reconciliação.
Relaxe garantias quando:
Controle risco definindo invariantes (o que nunca pode ser violado), projetando operações idempotentes, usando timeouts e retries com backoff, e monitorando deriva (sagas presas, compensações repetidas, tabelas outbox crescendo). Para invariantes verdadeiramente críticas (por exemplo, “nunca gastar além do saldo”), mantenha-as dentro de um único serviço e uma única transação de banco quando possível.
Uma transação pode ser “correta” num teste unitário e ainda falhar sob tráfego real, reinícios e concorrência. Use este checklist para manter garantias ACID alinhadas com o comportamento em produção.
Comece escrevendo o que deve sempre ser verdade (suas invariantes de dados). Exemplos: “saldo nunca fica negativo”, “total do pedido é igual à soma dos itens”, “inventário não pode ficar abaixo de zero”, “um pagamento está vinculado a exatamente um pedido.” Trate isso como regras de produto, não apenas trivia de banco.
Depois decida o que precisa estar dentro de uma transação versus o que pode ser adiado.
Mantenha transações pequenas: toque menos linhas, faça menos trabalho (sem chamadas externas) e comite rápido.
Faça concorrência uma dimensão de teste de primeira classe.
Se você suporta retries, adicione uma chave de idempotência explícita e teste “requisição repetida após sucesso”.
Monitore indicadores de que suas garantias estão ficando caras ou frágeis:
Alarme em tendências, não só picos, e vincule métricas aos endpoints ou jobs que as causam.
Use o isolamento mais fraco que ainda proteja suas invariantes; não “aumente ao máximo” por padrão. Quando precisar de corretude estrita para uma pequena seção crítica (movimentação de dinheiro, decremento de inventário), estreite a transação para só essa seção e mantenha todo o resto fora.
ACID é um conjunto de garantias transacionais que ajudam bancos de dados a se comportarem de forma previsível diante de falhas e concorrência:
Uma transação é uma única "unidade de trabalho" que o banco de dados trata como um pacote. Mesmo que execute várias instruções SQL (por exemplo: criar pedido, decrementar inventário, registrar intenção de pagamento), ela tem apenas dois resultados:
Porque atualizações parciais criam contradições no mundo real que são caras de corrigir depois — por exemplo:
ACID (especialmente atomicidade + consistência) evita que esses estados “meio prontos” fiquem visíveis como verdade.
Atomicidade garante que o banco de dados nunca exponha uma transação “meio completa”. Se algo falhar antes do commit — crash da aplicação, perda de rede, reinício do BD — a transação é revertida para que passos anteriores não vazem para o estado persistente.
Na prática, atomicidade é o que torna mudanças multi-etapa (como uma transferência que atualiza dois saldos) seguras.
Nem sempre é possível saber se houve commit quando o cliente perde a resposta (por exemplo, timeout de rede logo após o commit). Combine transações ACID com:
Isso evita tanto atualizações parciais quanto cobranças/gravacoes duplicadas acidentais.
Em ACID, “consistência” significa que o banco de dados move de um estado válido para outro de acordo com regras que você define — constraints, chaves estrangeiras, unicidade e checks.
Se você não codificar uma regra (por exemplo, “saldo não pode ficar negativo”), ACID não irá impedi-la automaticamente. O banco precisa de invariantes explícitas para proteger esse comportamento.
A validação no aplicativo melhora a experiência do usuário e pode aplicar regras complexas, mas pode falhar sob concorrência (duas requisições passam na checagem ao mesmo tempo).
Constraints do banco são o guardião final:
Use ambos: valide cedo na aplicação, e aplique a garantia definitivamente no banco.
Isolamento controla o que sua transação pode observar enquanto outras rodam. Isolamento fraco pode produzir anomalias como:
Níveis de isolamento permitem trocar desempenho por proteção contra essas anomalias.
Um ponto de partida prático é Read Committed para muitas aplicações OLTP: evita dirty reads com bom desempenho. Suba quando necessário:
Sempre confirme o comportamento no seu motor de BD específico, porque os detalhes variam.
Durabilidade significa que, uma vez que o banco confirma um commit, a mudança sobreviverá a falhas. Normalmente isso é implementado via write-ahead logging (WAL) e checkpoints.
Fique atento às escolhas de configuração:
Backups e replicação ajudam recuperação/disponibilidade, mas não são a mesma garantia que durabilidade.