Um SAVEPOINT é um marcador nomeado dentro de uma transação
Uma transação comum funciona no esquema tudo-ou-nada: tudo o que está entre o BEGIN e o COMMIT é gravado junto ou descartado junto. Na maioria das vezes é exatamente isso que você quer — mas, em algumas situações, faz falta um controle mais granular. Algo como: "vou tentar essa leva de alterações; se der ruim, desfaço só essa parte e mantenho o resto da transação viva".
É aí que entra o savepoint do SQLite. Você cria um marcador nomeado, executa um trecho de trabalho e depois decide: ou confirma esse pedaço com RELEASE, ou volta até o marcador com ROLLBACK TO.
O débito da Ada e o crédito do Boris permanecem aplicados. Já o UPDATE errado em Nobody foi desfeito sem perder o resto da transação.
Os três comandos
A API toda se resume a três comandos:
SAVEPOINT nome— cria um marcador.RELEASE SAVEPOINT nome— confirma o que foi feito desde o marcador e descarta o marcador.ROLLBACK TO SAVEPOINT nome— desfaz tudo que aconteceu depois do marcador, mas mantém o marcador no lugar para você tentar de novo.
A palavra SAVEPOINT depois de RELEASE e ROLLBACK TO é opcional — tanto RELEASE risky quanto ROLLBACK TO risky funcionam normalmente.
A linha tentativa do passo 2 simplesmente não existe do ponto de vista do banco final. Todo o resto é persistido.
Savepoints sem uma transação externa
Aqui vai um detalhe curioso: dá pra usar SAVEPOINT sem precisar de um BEGIN antes. O SQLite abre uma transação por baixo dos panos, e o savepoint mais externo acaba fazendo o papel da própria transação. Um RELEASE nesse savepoint faz o commit, enquanto um ROLLBACK TO desfaz tudo sem commitar.
É por isso que savepoints são às vezes descritos como "transações nomeadas". Mas misturar os dois estilos no mesmo código vira uma bagunça — escolha um. A maioria das pessoas usa BEGIN ... COMMIT explícito para delimitar a transação externa e deixa os savepoints só para os pontos internos onde pode ser preciso desfazer parcialmente.
Savepoints aninhados no SQLite
Savepoints empilham. Você pode criar um dentro do outro e fazer rollback do interno sem mexer no externo:
Resultado final: a, b, d. O ROLLBACK TO SAVEPOINT inner desfez o c, mas preservou o que foi feito antes do inner (o insert do b), e a transação seguiu seu curso.
Voltar para um savepoint externo também descarta tudo o que foi feito nos níveis internos — toda a pilha acima daquele nome é desfeita de uma só vez:
Tanto b quanto c desapareceram. O ROLLBACK TO outer desfaz tudo o que aconteceu depois que outer foi criado, incluindo o inner e o insert do c.
Quando usar SAVEPOINT no SQLite?
O caso clássico é o processamento em lote, onde itens individuais podem falhar sem comprometer o lote inteiro. A ideia é envolver cada item em um savepoint: se algo der errado, você faz rollback até aquele savepoint e segue em frente:
Em código de aplicação real, o INSERT problemático dispara um erro, a aplicação captura essa exceção, executa ROLLBACK TO e segue em frente. As duas linhas válidas são gravadas; a linha ruim não contamina o lote.
Esse padrão também é como ORMs e ferramentas de migração implementam transações aninhadas — eles não aninham blocos BEGIN de verdade (o SQLite não permite isso), mas sim mapeiam chamadas aninhadas para savepoints.
Alguns detalhes para ficar de olho
Algumas pegadinhas que costumam confundir quem está começando com savepoints:
COMMITsempre confirma a transação inteira. Não importa quantos savepoints estejam abertos —COMMIT(ou seu apelidoEND) fecha a transação externa por completo. Não encareRELEASEcomo um commit parcial; nada se torna durável até a transação que envolve tudo ser commitada.ROLLBACK(semTO) aborta tudo. Encerra a transação e descarta todos os savepoints abertos. Quando você quiser manter a transação viva, useROLLBACK TO nome.- Um savepoint permanece aberto até ser liberado ou desfeito por um rollback. Esquecer de dar
RELEASEnão causa perda de dados, mas o marcador fica ali parado até a transação terminar. - Os nomes não precisam ser únicos. Se você criar
SAVEPOINT sduas vezes,ROLLBACK TO svai encontrar o mais recente. Útil para recursão; confuso se acontecer sem querer.
A seguir: Views
Savepoints dão controle mais fino sobre as escritas. O próximo passo é moldar como você lê — salvar uma consulta como um objeto nomeado e reutilizável, do qual você pode dar SELECT como se fosse uma tabela. Isso é uma view, e é o que vem em seguida.
Perguntas frequentes
O que é um savepoint no SQLite?
É um marcador nomeado que você cria dentro de uma transação. Depois, dá para usar ROLLBACK TO com esse nome para desfazer tudo o que rolou após o marcador, ou RELEASE para manter as alterações e descartar o marcador. Na prática, savepoints permitem tratar partes da transação como blocos menores e recuperáveis.
Qual a diferença entre savepoint e transação no SQLite?
Uma transação começa com BEGIN e termina em COMMIT ou ROLLBACK. Já o savepoint é criado dentro dessa transação, com SAVEPOINT nome, e funciona como um ponto de desfazer parcial. Voltar até um savepoint não encerra a transação que o envolve — você continua trabalhando normalmente e faz o commit depois.
Dá para aninhar savepoints no SQLite?
Dá sim. Você pode empilhar vários savepoints com nomes diferentes, e um ROLLBACK TO outer desfaz tudo até aquele nível, inclusive os savepoints internos. Os nomes nem precisam ser únicos: o SQLite usa o savepoint mais recente com aquele nome, seguindo o comportamento clássico de pilha.