Pesquisar conteúdos

Encontre uma trilha ou aula pelo assunto.

Experimente pesquisar por “governança”, “ITIL” ou “planejamento”.

Transações e propriedades ACID

Entenda como transações agrupam operações e como atomicidade, consistência, isolamento e durabilidade protegem resultados.
Um estado relacional inicial atravessa uma unidade transacional selada e sustentada por quatro garantias antes de seguir para commit ou retornar por rollback

Uma operação de negócio raramente cabe em uma única alteração. Publicar um conteúdo pode mudar seu estado, registrar quem aprovou e criar um evento de auditoria. Se apenas parte dessas etapas permanecer, o banco passa a contar uma história contraditória.

Uma transação delimita essa unidade de trabalho. Ela permite confirmar todas as mudanças com COMMIT ou descartá-las com ROLLBACK, enquanto o SGBD aplica garantias de atomicidade, consistência, isolamento e durabilidade — as propriedades ACID.

Na aula anterior, aprendemos a modificar dados e validar estados com constraints. Agora, a pergunta é outra: quais operações precisam ter um único destino?

Ao final, você deverá conseguir:

  • definir uma fronteira transacional coerente com o requisito;
  • explicar o efeito de BEGIN, COMMIT e ROLLBACK;
  • reconhecer quando o autocommit separa etapas que deveriam permanecer juntas;
  • diferenciar as quatro propriedades ACID sem atribuir garantias inexistentes;
  • usar savepoints para recuperação parcial dentro da mesma transação;
  • entender constraints adiáveis e o momento da validação;
  • separar efeitos do banco de efeitos externos, como HTTP e envio de mensagens;
  • reconhecer transações longas, repetição cega e resultado de commit desconhecido como riscos operacionais.
BEGIN parte de um estado inicial, uma transação aberta reúne três instruções e termina em COMMIT, que confirma a unidade, ou ROLLBACK, que descarta as mudanças
A fronteira transacional reúne instruções em uma decisão. Estados intermediários não devem escapar como se fossem o resultado final.

Transação é uma unidade de trabalho com uma decisão final

Considere uma publicação formada por duas mudanças:

BEGIN;

UPDATE conteudo
SET
  situacao = 'publicado',
  publicado_em = CURRENT_TIMESTAMP
WHERE conteudo_id = 42
  AND situacao = 'revisao';

INSERT INTO evento_publicacao (
  conteudo_id,
  tipo,
  registrado_em
)
VALUES (
  42,
  'conteudo_publicado',
  CURRENT_TIMESTAMP
);

COMMIT;

O requisito não é “executar dois comandos”. É produzir um estado em que o conteúdo publicado e seu evento existam juntos. A transação deve acompanhar essa unidade semântica.

Se o INSERT falhar, confirmar apenas o UPDATE viola o requisito. A aplicação deve encerrar a unidade com ROLLBACK e tratar a causa.

BEGIN abre; COMMIT confirma; ROLLBACK descarta

No PostgreSQL, BEGIN e START TRANSACTION iniciam um bloco explícito:

BEGIN;
-- uma ou mais instruções
COMMIT;

Enquanto o bloco está aberto, as mudanças pertencem à transação. A visibilidade para outras sessões depende das regras de isolamento, mas elas ainda não foram confirmadas.

COMMIT é a decisão de tornar o efeito permanente

COMMIT;

Quando o SGBD confirma o sucesso, as mudanças são encerradas como unidade e recebem a garantia de durabilidade definida pelo sistema. COMMIT não significa “salvar uma instrução”; ele encerra toda a transação atual.

Depois do commit, um ROLLBACK posterior não desfaz essa transação. Uma correção exigirá uma nova operação compensatória ou restauração apropriada.

ROLLBACK cancela as alterações da transação aberta

ROLLBACK;

ROLLBACK descarta os efeitos produzidos desde o início do bloco. Ele não volta o banco para um horário arbitrário, não desfaz transações já confirmadas e não substitui backup.

Erro técnico não deve deixar a decisão implícita

No PostgreSQL, quando uma instrução causa erro dentro de um bloco, a transação entra em estado abortado. As instruções seguintes são rejeitadas até que ocorra ROLLBACK ou uma recuperação por savepoint apropriado.

O fluxo da aplicação precisa garantir encerramento em todos os caminhos:

abrir transação
  tentar operações
    se todas forem válidas: commit
    se alguma falhar: rollback
liberar conexão

Use o mecanismo transacional da biblioteca ou framework quando ele oferece esse padrão. Misturar controle manual e automático sem conhecer a conexão real pode confirmar ou desfazer a unidade errada.

Autocommit cria uma transação por instrução

No PostgreSQL, uma instrução executada fora de BEGIN ainda ocorre dentro de uma transação implícita. Se tiver sucesso, ela é confirmada ao final. Esse comportamento costuma ser chamado de autocommit.

UPDATE conteudo
SET situacao = 'publicado'
WHERE conteudo_id = 42;

INSERT INTO evento_publicacao (conteudo_id, tipo)
VALUES (42, 'conteudo_publicado');

Com autocommit, o primeiro comando pode ser confirmado antes de o segundo começar. Se o INSERT falhar, o UPDATE já permaneceu. Duas instruções consecutivas não formam uma única transação apenas porque aparecem juntas no código.

Clientes e frameworks podem controlar autocommit de modos diferentes. Verifique:

  • quem abre a transação;
  • qual conexão executa cada comando;
  • quem decide commit ou rollback;
  • o que acontece quando uma exceção atravessa a camada de acesso;
  • se a conexão retorna limpa ao pool.

ACID descreve quatro propriedades complementares

O acrônimo ACID consolidou uma linguagem para discutir transações confiáveis. As propriedades não são quatro funcionalidades isoladas; elas descrevem como a unidade se comporta diante de falhas, regras e concorrência.

Quatro painéis mostram atomicidade como três etapas confirmadas juntas, consistência como passagem entre estados válidos, isolamento como duas linhas concorrentes coordenadas e durabilidade como um registro persistente após confirmação
ACID não promete que toda decisão do sistema está correta. Ele define contratos para a transação; aplicação, esquema, configuração e operação precisam usá-los adequadamente.

Atomicidade: tudo ou nada na unidade

Atomicidade impede que uma transação termine com apenas parte de seus efeitos confirmados no banco.

Imagine a conclusão de uma trilha:

  1. marcar a matrícula como concluída;
  2. registrar a data de conclusão;
  3. emitir o registro do certificado.

Se a terceira etapa falhar, a transação pode ser desfeita integralmente. Para outras transações, o resultado confirmado é a unidade completa ou nenhum efeito dela.

Atomicidade não significa que cada instrução está correta. Uma transação pode confirmar, de forma atômica, uma regra de negócio errada. Também não inclui automaticamente efeitos executados fora do banco, como um e-mail já enviado.

Consistência: a transação preserva invariantes

Consistência significa que uma transação correta leva o banco de um estado válido para outro estado válido, respeitando os invariantes modelados e a lógica da operação.

Exemplos de invariantes:

  • uma primary key identifica uma linha;
  • uma foreign key aponta para uma referência existente;
  • um percentual permanece entre 0 e 100;
  • um conteúdo publicado possui data de publicação;
  • uma conclusão não pode preceder a matrícula.

O SGBD consegue impor as regras declaradas por tipos, constraints, triggers e outros mecanismos. Regras ausentes ou mal modeladas não surgem por causa do “C” de ACID.

Uma transação também pode conter lógica incorreta que satisfaz todas as constraints — por exemplo, creditar pontos ao estudante errado usando um identificador válido. O estado é estruturalmente aceito, mas semanticamente incorreto.

Consistência em ACID não é consistência entre réplicas

O termo “consistência” aparece em outros contextos: replicação, sistemas distribuídos, modelos de leitura e teorema CAP. Esses conceitos se relacionam, mas não são sinônimos automáticos da propriedade ACID. Nesta aula, consistência significa preservar os invariantes da unidade transacional.

Isolamento: concorrência com efeitos controlados

Várias transações podem executar ao mesmo tempo. Isolamento define quais efeitos intermediários e mudanças concorrentes uma transação pode observar e qual resultado é permitido.

O ideal mais forte é que o efeito confirmado seja equivalente a alguma execução serial — uma transação inteira depois da outra — mesmo que o SGBD tenha trabalhado concorrentemente. Níveis menos estritos permitem determinadas anomalias em troca de outras características de desempenho e concorrência.

Isolamento não significa que:

  • apenas uma transação executa por vez;
  • toda consulta mantém a mesma fotografia em qualquer nível;
  • bloqueios nunca acontecem;
  • conflitos desaparecem;
  • a aplicação nunca precisa repetir uma transação.

A próxima aula será canônica para níveis de isolamento, leituras não repetíveis, fantasmas, anomalias de serialização, locks, deadlocks e MVCC. Por enquanto, guarde que uma transação precisa ser projetada para o nível real do SGBD, não para uma execução imaginária sem concorrência.

Durabilidade: commit confirmado sobrevive às falhas cobertas

Depois que o banco confirma o COMMIT, durabilidade exige que o resultado permaneça mesmo se ocorrer uma falha de sistema coberta pelo mecanismo de recuperação.

SGBDs normalmente usam registro antecipado de alterações, armazenamento persistente e recuperação para cumprir essa promessa. A aplicação não precisa esperar cada página de tabela ser gravada diretamente antes do commit; ela depende do protocolo de persistência do SGBD.

Durabilidade não equivale a:

  • disponibilidade: o banco pode estar temporariamente inacessível e ainda preservar os dados;
  • backup: corrupção lógica, exclusão confirmada e desastre amplo exigem cópias e restauração;
  • replicação síncrona universal: a confirmação em réplicas depende da arquitetura e configuração;
  • retenção eterna: políticas legítimas podem excluir dados depois;
  • confirmação de efeitos externos: o banco não garante que um serviço HTTP recebeu a solicitação.

Configurações que mudam o momento em que um commit é reconhecido podem alterar o risco aceito. Não trate ajustes de persistência como otimização neutra.

O exemplo completo precisa verificar o resultado

Uma transação não corrige a cardinalidade de um UPDATE. O código ainda deve conferir se a transição ocorreu:

BEGIN;

UPDATE conteudo
SET
  situacao = 'publicado',
  publicado_em = CURRENT_TIMESTAMP
WHERE conteudo_id = 42
  AND situacao = 'revisao'
RETURNING conteudo_id, publicado_em;

Se nenhuma linha for retornada, a aplicação não deve inserir o evento como se houvesse publicação. Ela deve investigar o estado e normalmente executar ROLLBACK.

Se uma linha for retornada:

INSERT INTO evento_publicacao (
  conteudo_id,
  tipo,
  registrado_em
)
VALUES (
  42,
  'conteudo_publicado',
  CURRENT_TIMESTAMP
);

COMMIT;

O fluxo combina responsabilidades diferentes:

  • o predicado protege a transição esperada;
  • RETURNING evidencia o resultado;
  • constraints validam invariantes;
  • a transação une os efeitos;
  • o código decide confirmar apenas depois das verificações.

SAVEPOINT cria um ponto de retorno interno

Um savepoint permite desfazer apenas as instruções executadas depois de um marco:

BEGIN;

UPDATE matricula
SET situacao = 'concluida'
WHERE matricula_id = 800;

SAVEPOINT antes_do_certificado;

INSERT INTO certificado (matricula_id, codigo)
VALUES (800, 'CODIGO-INCORRETO');

ROLLBACK TO SAVEPOINT antes_do_certificado;

INSERT INTO certificado (matricula_id, codigo)
VALUES (800, 'CERT-800-2026');

RELEASE SAVEPOINT antes_do_certificado;

COMMIT;

O primeiro INSERT é descartado; o UPDATE anterior permanece dentro da transação; o segundo INSERT pode ser confirmado junto com ele.

Uma linha do tempo parte de BEGIN, preserva uma alteração A, marca SAVEPOINT s1, desfaz a tentativa B até esse marco, executa uma alternativa C e confirma tudo com COMMIT
ROLLBACK TO recupera um trecho. RELEASE remove o marco, mas não confirma nada: A e C continuam dependentes do COMMIT externo.

Savepoint não é uma transação independente

  • ROLLBACK TO preserva as mudanças anteriores ao marco;
  • o savepoint nomeado continua disponível no PostgreSQL;
  • savepoints criados depois dele são descartados;
  • RELEASE SAVEPOINT remove o marco e conserva os efeitos posteriores;
  • somente o COMMIT da transação externa torna o conjunto durável.

No PostgreSQL, um savepoint também pode permitir recuperar um bloco que entrou em estado de erro, desde que o marco tenha sido criado antes da instrução problemática. Bibliotecas frequentemente implementam blocos transacionais aninhados com savepoints; isso não cria commits internos autônomos.

Use savepoint quando existe uma alternativa legítima dentro da mesma unidade. Não o use para ignorar qualquer erro e confirmar um estado que deixou de ser compreendido.

Constraints adiáveis permitem estados intermediários controlados

Algumas operações precisam passar temporariamente por um estado que violaria uma regra, embora o estado final seja válido. No PostgreSQL, constraints UNIQUE, PRIMARY KEY, EXCLUDE e FOREIGN KEY podem ser declaradas adiáveis; NOT NULL e CHECK não usam esse mecanismo.

Suponha uma ordenação única por trilha:

CONSTRAINT uq_modulo_ordem
  UNIQUE (trilha_id, ordem)
  DEFERRABLE INITIALLY IMMEDIATE

Dentro da transação, a validação pode ser adiada:

BEGIN;

SET CONSTRAINTS uq_modulo_ordem DEFERRED;

UPDATE modulo
SET ordem = CASE
  WHEN ordem = 1 THEN 2
  WHEN ordem = 2 THEN 1
END
WHERE trilha_id = 10
  AND ordem IN (1, 2);

SET CONSTRAINTS uq_modulo_ordem IMMEDIATE;

COMMIT;

Ao voltar para IMMEDIATE, o PostgreSQL verifica retroativamente a constraint. Se o estado ainda for inválido, o comando falha. Adiar a verificação não autoriza confirmar inconsistência; apenas muda o momento do teste.

A transação do banco não controla efeitos externos

Considere este fluxo:

  1. atualizar a matrícula no banco;
  2. enviar uma requisição HTTP ao serviço de certificados;
  3. confirmar a transação.

Se o serviço emitir o certificado e o commit falhar, ROLLBACK não desfaz a chamada HTTP. Se o commit ocorrer e a resposta HTTP se perder, repetir pode emitir duas vezes.

Uma estratégia comum é registrar, na mesma transação, um evento pendente em uma tabela de saída:

BEGIN;

WITH conclusao AS (
  UPDATE matricula
  SET situacao = 'concluida'
  WHERE matricula_id = 800
    AND situacao = 'ativa'
  RETURNING matricula_id
)
INSERT INTO evento_saida (
  chave_idempotencia,
  tipo,
  payload
)
SELECT
  'certificado-matricula-' || matricula_id,
  'emitir_certificado',
  jsonb_build_object('matricula_id', matricula_id)
FROM conclusao;

COMMIT;

No PostgreSQL, a CTE de modificação faz o evento nascer apenas quando a transição retorna uma matrícula. Outro processo lê eventos confirmados e chama o serviço externo com uma chave idempotente. Esse padrão, frequentemente chamado de transactional outbox, reduz a lacuna entre mudança no banco e publicação do evento, mas ainda exige repetição, deduplicação, monitoramento e tratamento de falhas.

Não faça uma chamada de rede lenta enquanto mantém locks e uma transação aberta sem necessidade. A fronteira do banco deve conter os dados que precisam de atomicidade; coordenação externa precisa de protocolo próprio.

Transações longas aumentam custo e conflito

Uma transação aberta por muito tempo pode:

  • manter locks e bloquear outras operações;
  • ampliar a janela para deadlocks e conflitos;
  • reter versões antigas necessárias à visibilidade;
  • atrasar limpeza e manutenção no PostgreSQL;
  • consumir conexão do pool;
  • acumular trabalho que será descartado em uma falha;
  • tornar retry mais caro.

Evite abrir uma transação antes de:

  • pedir confirmação ao usuário;
  • processar arquivo pesado que poderia ser validado antes;
  • chamar APIs externas;
  • executar cálculos sem acesso ao estado protegido;
  • aguardar filas ou temporizadores.

“Manter curta” não significa dividir uma unidade necessária em commits parciais. Significa preparar o que puder fora e manter dentro apenas as operações e verificações que precisam da mesma fronteira.

Falha de conexão pode deixar o resultado do COMMIT desconhecido

Há uma diferença entre:

  • rollback confirmado: a transação não foi aplicada;
  • commit confirmado: a transação foi aplicada;
  • conexão perdida durante a confirmação: o cliente pode não saber qual decisão o servidor concluiu.

Repetir cegamente uma operação não idempotente pode duplicar cobrança, certificado ou evento. Use identificadores estáveis de operação, constraints de unicidade e consulta de reconciliação para descobrir o resultado.

Exemplo:

INSERT INTO evento_saida (
  chave_idempotencia,
  tipo,
  payload
)
VALUES (
  $1,
  'emitir_certificado',
  $2
)
ON CONFLICT (chave_idempotencia) DO NOTHING;

A chave precisa representar a operação de negócio, não uma tentativa aleatória. Idempotência também não substitui transação: ela torna repetições observáveis ou inofensivas segundo um contrato.

Erros comuns

  • usar autocommit em etapas inseparáveis: a primeira mudança pode permanecer quando a segunda falha;
  • abrir uma transação por função, não por requisito: efeitos relacionados ficam separados ou efeitos independentes ficam presos juntos;
  • confirmar sem verificar linhas afetadas: a transação pode registrar um evento para uma mudança que não ocorreu;
  • achar que atomicidade valida o negócio: tudo pode ser confirmado de forma atomicamente errada;
  • tratar consistência como regra automática: somente invariantes declarados e lógica correta são preservados;
  • entender isolamento como execução sem concorrência: níveis e mecanismos permitem comportamentos diferentes;
  • confundir durabilidade com backup ou disponibilidade: são objetivos distintos;
  • continuar após erro em transação abortada no PostgreSQL: é necessário rollback total ou até savepoint;
  • imaginar savepoint como commit interno: todo o trabalho ainda depende da decisão externa;
  • adiar constraints e esquecer a validação final: o commit rejeitará o estado inválido;
  • chamar serviço externo dentro da transação sem protocolo: rollback não desfaz efeitos fora do banco;
  • manter transação aberta durante interação humana: locks, versões e conexões ficam retidos;
  • repetir após conexão perdida sem reconciliar: o commit pode ter ocorrido;
  • devolver conexão abortada ao pool: o próximo consumidor recebe um estado transacional inválido.

Checklist de uma fronteira transacional

  1. Qual resultado de negócio precisa ser tudo ou nada?
  2. Todas as tabelas e operações usam a mesma conexão e transação?
  3. O autocommit está desativado durante a unidade?
  4. Cada transição verifica estado anterior e cardinalidade?
  5. Constraints cobrem os invariantes persistentes possíveis?
  6. Todo caminho de erro executa rollback ou recuperação até savepoint?
  7. O commit só ocorre depois de todas as verificações?
  8. A aplicação distingue zero linhas, violação de regra e falha técnica?
  9. O nível de isolamento real sustenta as suposições feitas?
  10. Conflitos serializáveis ou deadlocks possuem política de retry?
  11. Savepoints representam alternativas legítimas, e não supressão de erros?
  12. Constraints adiáveis são realmente necessárias e voltam a ser verificadas?
  13. Existe chamada de rede, espera humana ou processamento evitável dentro da transação?
  14. Efeitos externos usam outbox, idempotência ou outra coordenação explícita?
  15. Uma perda de conexão durante commit pode ser reconciliada por chave de negócio?
  16. A conexão é sempre devolvida ao pool em estado conhecido?

O que você deve guardar

Uma transação transforma várias instruções em uma decisão: confirmar todas ou descartar todas. Atomicidade protege a unidade, consistência depende de invariantes e lógica correta, isolamento controla a interação concorrente e durabilidade preserva o commit diante das falhas cobertas. Savepoints refinam a recuperação interna, mas não criam commits independentes.

Na próxima aula, veremos como concorrência, níveis de isolamento, locks, deadlocks e MVCC determinam o que transações simultâneas podem observar e como seus conflitos são resolvidos.

Referências