Transações SQL e ACID explicados: COMMIT, ROLLBACK e níveis de isolamento

  1. Início
  2. Blog
  3. Bancos de dados e SQL

Bancos de dados e SQL

Transações SQL e ACID explicados: COMMIT, ROLLBACK e níveis de isolamento

O que acontece se o servidor falhar no meio da movimentação de dinheiro entre duas contas? As transações são a resposta. Veja como funcionam em um exemplo real, o que significa ACID e como escolher um nível de isolamento.

9 min de leitura
Transações SQL e ACID explicadas: COMMIT, ROLLBACK e níveis de isolamento

Principais pontos

  • Uma transação agrupa instruções para que todas tenham sucesso juntas ou todas falhem juntas.
  • COMMIT torna as alterações permanentes e ROLLBACK as cancela.
  • ACID significa atomicidade, consistência, isolamento e durabilidade.
  • Um isolamento mais forte evita mais anomalias, mas pode reduzir a simultaneidade, portanto escolha deliberadamente.

Mover 300 rúpias de Asha para Ben exige duas atualizações: subtrair de uma conta e adicionar à outra. Se o sistema falhar entre eles, o dinheiro desaparece. As transações SQL existem para tornar isso impossível, tratando ambas as etapas como uma única unidade.

Neste guia, analisamos commits, reversões e falhas em uma tabela de contas pequenas. Executamos esses exemplos no SQLite e os saldos mostrados vêm dessa execução. Os comandos básicos são semelhantes entre bancos de dados, mas o comportamento de bloqueio e isolamento varia muito, por isso apontamos onde verificar a documentação do seu próprio banco de dados.

O que é uma transação?

Uma transação é uma sequência de uma ou mais instruções SQL que o banco de dados trata como uma única operação lógica. Aqui está nossa tabela:

CREATE TABLE accounts (
  id INT PRIMARY KEY,
  owner TEXT,
  balance INT CHECK (balance >= 0)
);

INSERT INTO accounts VALUES (1, 'Asha', 1000), (2, 'Ben', 200);

A restrição CHECK é uma regra de que um saldo nunca pode ficar abaixo de zero, que usaremos mais tarde. Observe que a sintaxe para iniciar uma transação varia: SQLite e PostgreSQL aceitam BEGIN, MySQL também tem START TRANSACTION e SQL Server usa BEGIN TRANSACTION.

Fora de uma transação explícita, a maioria dos bancos de dados usa autocommit, onde cada instrução é salva assim que termina. Oracle é uma exceção notável, porque uma sessão permanece em uma transação até você confirmar.

COMMIT: torne-o permanente

BEGIN;
UPDATE accounts SET balance = balance - 300 WHERE id = 1;
UPDATE accounts SET balance = balance + 300 WHERE id = 2;
COMMIT;

-- Asha | 700
-- Ben  | 500

Até que COMMIT seja executado, as alterações são provisórias. Depois de retornar com sucesso, o banco de dados garante que eles foram salvos. O total em ambas as contas ficou em 1.200, que é o objetivo do exercício.

ROLLBACK: cancelar tudo

ROLLBACK desfaz tudo desde o início da transação:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
SELECT owner, balance FROM accounts;
-- Asha | 600   (visible inside this transaction)
-- Ben  | 500

ROLLBACK;
SELECT owner, balance FROM accounts;
-- Asha | 700   (back to where it was)
-- Ben  | 500

Uma reversão também acontece automaticamente se a conexão cair ou o servidor travar antes de você confirmar, portanto, uma transação pela metade não sobreviverá.

Quando uma declaração falha

Aqui está um caso mais realista. Asha tenta enviar 5.000, mas a conta não possui. O crédito para Ben é executado primeiro e depois o débito quebra a regra CHECK:

BEGIN;
UPDATE accounts SET balance = balance + 5000 WHERE id = 2;
UPDATE accounts SET balance = balance - 5000 WHERE id = 1;
-- error: CHECK constraint failed: balance >= 0

ROLLBACK;

-- Asha | 700
-- Ben  | 500

Sem a transação, Ben teria ficado com os 5.000 e os livros estariam errados. Esteja ciente de um detalhe importante: uma instrução com falha nem sempre cancela a transação por si só. No SQLite e no MySQL, por exemplo, instruções anteriores podem permanecer pendentes, então seu código precisa emitir o rollback. Em vez disso, o PostgreSQL marca toda a transação como abortada e recusa instruções adicionais até que você reverta.

No código do aplicativo, sempre emparelhe a transação com o tratamento de erros. Aqui está a ideia em Python, e nosso guia para tratamento de exceções em Python explica a sintaxe:

try:
    conn.execute("BEGIN")
    conn.execute(debit_sql, values)
    conn.execute(credit_sql, values)
    conn.execute("COMMIT")
except Exception:
    conn.execute("ROLLBACK")
    raise

Pontos de salvamento

Um savepoint marca uma posição dentro de uma transação, então você pode desfazer apenas parte do trabalho:

BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
SAVEPOINT bonus;
UPDATE accounts SET balance = balance + 999 WHERE id = 2;
ROLLBACK TO bonus;
UPDATE accounts SET balance = balance + 50 WHERE id = 2;
COMMIT;

-- Asha | 650
-- Ben  | 550

O bônus de 999 foi desfeito, enquanto o restante da transação foi continuado e comprometido. Isto é útil para etapas opcionais em um processo longo.

Propriedades ACID explicadas

Propriedade Promessa Em nossa transferência
Atomicidade Tudo ou nada Ambas as atualizações acontecem, ou nenhuma delas
Consistência As regras ainda valem depois Nenhum equilíbrio pode quebrar a regra CHECK
Isolamento O trabalho simultâneo não interfere Outros não veem uma transferência incompleta
Durabilidade Os dados confirmados sobrevivem à falha Um corte de energia após COMMIT não perde nada

A consistência é comumente mal compreendida. O banco de dados aplica apenas as regras que você declara, como restrições e chaves estrangeiras. Isso não impedirá que seu código transfira o valor errado. A durabilidade geralmente é alcançada com um log write-ahead, onde as alterações são registradas com segurança no disco antes da confirmação do commit.

O que acontece com transações simultâneas?

Abrimos duas conexões para um arquivo SQLite. O primeiro iniciou uma transação e atualizou uma linha de 1 para 2, sem fazer commit ainda. Aqui está o que a segunda conexão viu:

Connection B reads:   1                     -- no uncommitted data
Connection B writes:  database is locked    -- it has to wait
(A commits)
Connection B reads:   2                     -- now it sees the change

SQLite permite apenas um gravador por vez, o que é mais simples que bancos de dados de servidor, onde muitas transações podem escrever linhas diferentes simultaneamente. Mas o princípio é o mesmo: outras sessões não devem ver alterações não confirmadas em configurações normais, e gravações conflitantes terão que esperar. Em nosso teste, configuramos a segunda conexão para desistir imediatamente em vez de esperar, então o erro apareceu imediatamente.

Ler anomalias

Quando as transações se sobrepõem, certos problemas podem ocorrer. O padrão SQL define isso em termos do que uma transação pode observar:

Anomalia O que acontece
Leitura suja Você lê uma alteração que outra transação não foi confirmada e pode ser revertida posteriormente
Leitura não repetível Você lê uma linha duas vezes em uma transação e obtém valores diferentes, porque alguém fez commit entre elas.
Leitura fantasma Você executa a mesma consulta duas vezes e obtém linhas extras ou ausentes, porque alguém inseriu ou excluiu algumas

Outro problema é a atualização perdida. Duas sessões lêem um saldo de 100, cada uma adiciona 10 e cada uma escreve 110. Uma atualização efetivamente desapareceu, quando a resposta deveria ser 120.

Níveis de isolamento SQL

Os níveis de isolamento permitem trocar rigor por simultaneidade. Aqui está o que o padrão SQL permite em cada nível:

Nível Leitura suja Leitura não repetível Leitura fantasma
Leia sem compromisso Possível Possível Possível
Leitura confirmada Prevenido Possível Possível
Leitura repetível Prevenido Prevenido Possível
Serializável Prevenido Prevenido Prevenido
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

A sintaxe e o comportamento exatos dependem do seu banco de dados. Os padrões também diferem: PostgreSQL e SQL Server usam leitura confirmada, enquanto o InnoDB do MySQL usa leitura repetível. Alguns bancos de dados oferecem garantias mais fortes do que o exigido pelo padrão ou tratam um nível como outro. No PostgreSQL, por exemplo, a leitura não confirmada se comporta como a leitura confirmada. Sempre leia a documentação do seu banco de dados antes de confiar em um comportamento específico.

Dica: Para uma atualização perdida, faça a alteração em uma única instrução, como em UPDATE accounts SET balance = balance + 10. Fazer o cálculo em sua aplicação a partir de uma leitura anterior é o que abre a lacuna.

Impasses

Um impasse acontece quando duas transações mantêm, cada uma, um bloqueio que a outra precisa. A transação A bloqueia a linha 1 e deseja a linha 2, enquanto a transação B bloqueia a linha 2 e deseja a linha 1. Nenhuma delas pode continuar.

Bancos de dados como PostgreSQL, MySQL e SQL Server detectam isso e cancelam uma transação com erro. Esse é um comportamento normal e seu aplicativo deve estar preparado para isso. Para reduzir impasses, sempre atualize as linhas em uma ordem consistente, como primeiro o ID de conta mais baixo, mantenha as transações curtas e tente novamente quando uma delas for escolhida como vítima.

Melhores práticas

  • Mantenha as transações curtas. As longas bloqueiam e bloqueiam outros usuários.
  • Nunca espere por um usuário ou uma API lenta dentro de uma transação. Faça esse trabalho antes ou depois de começar.
  • Sempre lide com falhas. Todo caminho deve terminar em um commit ou rollback.
  • Adicione restrições. Elas fornecem ao banco de dados regras reais para serem aplicadas. Um bom design de mesa também ajuda.
  • Use bons índices. Pesquisas mais rápidas significam que os bloqueios são retidos por menos tempo.
  • Tenha cuidado com novas tentativas. Tente novamente em deadlocks, mas certifique-se de que uma operação não possa ser aplicada duas vezes.
  • Teste a simultaneidade propositalmente. Muitos desses bugs aparecem apenas sob carga, portanto, testes de integração básicos por si só podem não revelá-los.

Considerações finais

As transações transformam várias etapas arriscadas em uma operação segura, e o ACID descreve as garantias que tornam isso confiável. Sempre que uma ação comercial afetar mais de uma linha ou tabela, pergunte-se o que aconteceria se ela parasse no meio do caminho. Se a resposta o preocupa, envolva-a em uma transação. Para praticar, abra duas sessões em um banco de dados local e tente produzir você mesmo as anomalias.

Perguntas frequentes

O que é uma transação em SQL?

Uma transação é um grupo de instruções SQL tratadas como uma unidade. Ou todos eles são salvos com COMMIT, ou todos eles são desfeitos com ROLLBACK.

Quais são as propriedades ACID em um banco de dados?

ACID significa atomicidade, consistência, isolamento e durabilidade. Juntos, eles garantem que as transações sejam tudo ou nada, mantenham as regras intactas, não interfiram e sobrevivam a falhas.

Qual é a diferença entre COMMIT e ROLLBACK?

COMMIT salva permanentemente todas as alterações feitas na transação. ROLLBACK os cancela e retorna os dados como estavam antes do início da transação.

O que é uma leitura suja?

Uma leitura suja ocorre quando uma transação lê alterações e outra ainda não foi confirmada. Se essa outra transação for revertida, os dados lidos nunca existiram de verdade.

Qual nível de isolamento devo usar?

Comece com o padrão do seu banco de dados, o que geralmente é adequado. Use serializável para lógica crítica onde anomalias são inaceitáveis ​​e esteja pronto para tentar novamente transações com falha.

Continue aprendendo

Trabalhe com Stack Quora

Precisa de ajuda com testes, WordPress ou SEO?

Ajudamos as equipes a fornecer software melhor e a expandir seus sites. Conte-nos no que você está trabalhando e sugeriremos um próximo passo claro.

Continue lendo

Mais guias sobre testes, IA e desenvolvimento moderno

Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.

Inteligência artificial

Explicação das alucinações de IA: por que os modelos de linguagem inventam as coisas e como reduzi-las

Explicação das alucinações de IA: o que são, por que os modelos de linguagem as produzem, tipos comuns, como reduzi-las com aterramento e instruções…

Leia o artigo →
DevOps

Variáveis de ambiente explicadas: arquivos .env, segredos e configuração bem feitos

Variáveis de ambiente explicadas com exemplos de Python que executamos: leitura e configuração, por que cada valor é uma string, arquivos .env, hera…

Leia o artigo →
Desenvolvimento de software

Explicação da limitação de taxa de API: algoritmos, cabeçalhos e tratamento de erros 429

Limitação de taxa de API explicada com uma simulação que executamos: token bucket, janela fixa, janela deslizante, 429 respostas, cabeçalhos de limi…

Leia o artigo →
Automação de testes

Zombando em Python: unittest.mock, patch e os erros que fazem os testes mentirem

Zombaria em Python com unittest.mock, testado: Mock e return_value, side_effect, patch onde um nome é usado, especificação e autospec, zombaria de d…

Leia o artigo →
Teste de software

Cobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa

Cobertura de código explicada com uma execução cobertura.py real: cobertura de linha, ramal e condição, leitura de um relatório, por que 100% de cob…

Leia o artigo →
Bancos de dados e SQL

DELETE vs TRUNCATE vs DROP em SQL: diferenças, reversão e quando usar cada um

DELETE vs TRUNCATE vs DROP explicado com SQL testado: o que cada um remove, rollback, aciona, redefinição de identidade, velocidade e como PostgreSQ…

Leia o artigo →
Bancos de dados e SQL

Exibições SQL explicadas: crie, use e evite as armadilhas, com exemplos

Visualizações SQL explicadas com consultas que executamos: criação de uma visualização, resultados ao vivo, ocultação de colunas, desempenho, visual…

Leia o artigo →
Desenvolvimento de software

Interface TypeScript vs tipo: diferenças reais, testadas com o compilador

Interface TypeScript versus tipo, verificada com o compilador real: o que ambos podem fazer, o que apenas um pode fazer, mesclagem de declarações, e…

Leia o artigo →
Desenvolvimento de software

Python *args e **kwargs explicados: funções flexíveis com exemplos claros

Python *args e **kwargs explicados com o código que executamos: empacotamento e descompactação, ordem dos parâmetros, argumentos somente de palavras…

Leia o artigo →
Desenvolvimento de software

Funções Python Lambda: sintaxe, usos reais e quando não usá-los

Funções lambda do Python explicadas com o código que executamos: sintaxe, classificação com chave, mapa e filtro, fechamentos, armadilha de ligação …

Leia o artigo →
Inteligência artificial

O que são agentes de IA? Como eles funcionam, com um loop de agente simples que você pode executar

O que são agentes de IA? Um guia simples para o loop do agente, ferramentas, memória e proteções, com um pequeno loop Python que executamos, riscos …

Leia o artigo →
DevOps

Monitoramento versus observabilidade: a verdadeira diferença, com logs, métricas e rastreamentos

Explicação do monitoramento versus observabilidade: falhas conhecidas e desconhecidas, logs, métricas e rastreamentos, um passo a passo do incidente…

Leia o artigo →
DevOps

Docker Volumes vs Bind Mounts: persista os dados do contêiner da maneira certa

Volumes Docker versus montagens de ligação explicadas: por que os dados do contêiner desaparecem, volumes nomeados, montagens de ligação, tmpfs, exe…

Leia o artigo →
Automação de testes

TDD vs BDD: principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um

TDD vs BDD explicado com um recurso de desconto construído em ambos os sentidos: refatoração vermelho-verde com o código que executamos, cenários da…

Leia o artigo →
Teste de software

Teste de aceitação do usuário (UAT): processo, lista de verificação e exemplos reais

Explicação do teste de aceitação do usuário: quem o executa, como ele difere do controle de qualidade, um processo de sete etapas, um modelo de caso…

Leia o artigo →
Bancos de dados e SQL

Valores SQL NULL explicados: IS NULL, COALESCE, NULLIF e as armadilhas a serem evitadas

Valores SQL NULL explicados com consultas que executamos: lógica de três valores, IS NULL, COALESCE, NULLIF, como agregações, junções e classificaçã…

Leia o artigo →
Bancos de dados e SQL

Instrução SQL CASE WHEN: sintaxe, exemplos reais e erros comuns

SQL CASE WHEN explicado com consultas que executamos: formulários simples e pesquisados, ordem de condição, pivôs com SUM, truques COUNT, classifica…

Leia o artigo →
Desenvolvimento de software

O que é JWT? JSON Web Tokens explicados com um exemplo prático

O que é JWT? JSON Web Tokens explicados: as três partes, um token construído e adulterado no código que executamos, JWT vs sessões, armazenamento, t…

Leia o artigo →
Desenvolvimento de software

Loop de eventos JavaScript explicado: pilha de chamadas, microtarefas e temporizadores com exemplos

O loop de eventos JavaScript explicado com o código que executamos: pilha de chamadas, filas de tarefas e microtarefas, por que setTimeout 0 não é i…

Leia o artigo →
Desenvolvimento de software

Geradores Python e rendimento explicado: economize memória com exemplos reais

Geradores Python e rendimento explicados com o código que executamos: como eles pausam e reiniciam, medem economia de memória, pipelines, sequências…

Leia o artigo →
Inteligência artificial

O que são incorporações? Similaridade de vetores e cossenos explicada de forma simples

O que são incorporações? Um guia em linguagem simples para vetores, similaridade de cossenos e pesquisa semântica, com um pequeno exemplo que execut…

Leia o artigo →
DevOps

Implantação azul-verde vs canário: diferenças, compensações e como escolher

Explicação da implantação azul-verde versus canário: como cada um funciona, custo, risco e reversão, agrupamento estável de usuários que testamos, m…

Leia o artigo →
Automação de testes

Testes instáveis: o que os causa e como corrigi-los para sempre

Testes instáveis explicados com demonstrações que executamos: as principais causas, como confirmar e diagnosticar uma, soluções adequadas para tempo…

Leia o artigo →
Teste de software

Teste de carga versus teste de estresse: diferenças, métricas e exemplos k6

Teste de carga versus teste de estresse explicado: o que cada um prova, testes de pico e absorção, as métricas que importam, por que as médias engan…

Leia o artigo →
Teste de software

Caso de teste versus cenário de teste: principais diferenças, exemplos e quando usar cada um

Caso de teste vs cenário de teste explicado: definições claras, uma tabela lado a lado, um recurso de redefinição de senha retirado de cenários para…

Leia o artigo →
Bancos de dados e SQL

Otimização de consultas SQL: leia planos de execução e corrija consultas lentas, com intervalos

Otimização de consulta SQL testada em um milhão de linhas: leia planos de execução, adicione os índices corretos, corrija filtros não sargáveis, use…

Leia o artigo →
Bancos de dados e SQL

SQL UNION vs UNION ALL: diferenças, exemplos e desempenho testado

SQL UNION vs UNION ALL explicado com consultas que executamos: duplicatas, as regras que ambos seguem, um bug de dados real, desempenho medido, INTE…

Leia o artigo →
Desenvolvimento de software

Tutorial da API JavaScript Fetch: GET, POST, tratamento de erros e tempos limite

Um tutorial da API JavaScript Fetch testado em um servidor local: GET e POST, por que 404 não lança, um auxiliar reutilizável, strings de consulta, …

Leia o artigo →
Desenvolvimento de software

Manipulação de arquivos Python: leitura, gravação, CSV, JSON e pathlib com exemplos

Manipulação de arquivos Python explicada com código que executamos: modos abertos, com, leitura de arquivos grandes, CSV, JSON, pathlib, arquivos bi…

Leia o artigo →
Desenvolvimento de software

Classes e objetos Python: tutorial OOP com exemplos reais

Classes e objetos Python explicados com código que executamos: __init__, métodos, atributos de classe vs instância, herança, propriedades, métodos d…

Leia o artigo →
Inteligência artificial

O que é um modelo de linguagem grande? Como funcionam os LLMs, explicado sem exageros

O que é um modelo de linguagem grande? LLMs explicados em linguagem simples: tokens, incorporações, atenção, treinamento, temperatura e janelas de c…

Leia o artigo →
DevOps

Comandos Linux para iniciantes: o guia essencial do terminal com exemplos

Comandos Linux para iniciantes, agrupados por tarefa: navegação, arquivos, visualização de logs, grep, pipes, permissões, processos e arquivos, com …

Leia o artigo →
Automação de testes

Selenium vs Playwright: principais diferenças, código comparado e qual escolher

Selenium vs Playwright comparado: arquitetura, espera, suporte a navegador e linguagem, depuração, o mesmo teste em ambas as ferramentas, conselhos …

Leia o artigo →
Teste de software

Particionamento de equivalência: técnica de design de teste explicada com exemplos resolvidos

Explicação do particionamento de equivalência: como encontrar partições válidas e inválidas, exemplos resolvidos para idades, senhas e cupons, teste…

Leia o artigo →
Teste de software

Gravidade vs Prioridade em Testes de Software: Diferenças, Matriz e Exemplos Reais

Explicação da gravidade versus prioridade em testes de software: definições claras, uma matriz de quatro vias com exemplos, escalas de nível, quem d…

Leia o artigo →
Bancos de dados e SQL

Tutorial de subconsultas SQL: tipos, subconsultas correlacionadas e EXISTS com exemplos

Um tutorial de subconsultas SQL com consultas que executamos: subconsultas escalares, IN, FROM e SELECT, subconsultas correlacionadas, EXISTS, a arm…

Leia o artigo →
Bancos de dados e SQL

Transações SQL e ACID explicados: COMMIT, ROLLBACK e níveis de isolamento

Transações SQL e ACID explicadas com uma transferência bancária que executamos: COMMIT, ROLLBACK, savepoints, locks, níveis de isolamento, leituras …

Leia o artigo →
Desenvolvimento de software

Git merge vs rebase: diferenças, exemplos e quando usar cada um

Git merge vs rebase explicado com saída real do log, uma tabela comparativa, a regra de ouro do rebase e um fluxo de trabalho seguro para equipes.

Leia o artigo →
Desenvolvimento de software

List comprehension em Python: sintaxe, exemplos e quando evitar

List comprehension em Python explicada: sintaxe, filtros, if-else, laços aninhados, tempos medidos e quando um laço for comum é a melhor escolha.

Leia o artigo →
Bancos de dados e SQL

SQL JOIN explicado com exemplos (Inner, Left, Right, Full)

Entenda os joins SQL passo a passo com tabelas e consultas de exemplo: INNER, LEFT, RIGHT, FULL, CROSS e SELF, além de erros comuns e dicas de desem…

Leia o artigo →