SQL ROLLBACK e Savepoints
Na aula Transações, a Ana aprendeu a juntar vários comandos com BEGIN e gravá-los de uma vez com COMMIT. Falta a outra metade: e se, no meio da transação, ela perceber que fez besteira? Para isso existe o ROLLBACK ("voltar atrás"), que desfaz a transação inteira, como se ela nunca tivesse começado. E, quando só um pedaço deu errado, os savepoints ("pontos de salvamento") deixam desfazer só aquele pedaço e continuar.
- A desfazer uma transação inteira com
ROLLBACK. - A usar a transação como rede de proteção: testar um
UPDATEouDELETEperigoso, olhar o resultado e desfazer. - O que fazer quando um comando da transação dá erro.
- A marcar pontos dentro da transação com
SAVEPOINT, voltar a eles comROLLBACK TOe descartá-los comRELEASE. - O
INSERT OR ROLLBACK, que desfaz tudo sozinho quando dá erro. - O que o
ROLLBACKnão consegue desfazer, e as mensagens reais do SQLite.
Os exemplos com quadro "Resultado" rodam no SQLite do seu navegador. Como cada Rodar começa com o banco limpo, cada exemplo tem a transação inteira na mesma caixa: o BEGIN, as mudanças, o ROLLBACK e um SELECT depois, para você ver que tudo voltou. As partes em que um erro acontece no meio saíram do terminal da Ana, porque o editor para no primeiro erro.
A ideia: o "salvar" do videogame
Num videogame, você salva o jogo antes de entrar numa fase difícil. Se tudo der errado lá dentro, você não precisa recomeçar do zero: carrega o jogo salvo e está de volta à porta da fase, com tudo o que tinha conquistado antes.
| No videogame | No banco |
|---|---|
| Começar uma partida | BEGIN |
| Desistir da partida e voltar ao menu, sem gravar nada | ROLLBACK: desfaz tudo desde o BEGIN |
| Salvar antes da fase difícil | SAVEPOINT fase2: marca um ponto |
| Carregar o jogo salvo | ROLLBACK TO fase2: desfaz só o que veio depois da marca |
| Apagar o jogo salvo, que não é mais preciso | RELEASE fase2: tira a marca (sem desfazer nada) |
| Terminar a partida e gravar o recorde | COMMIT |
1. ROLLBACK: desfazer tudo
A Ana precisava apagar os itens de um pedido e escreveu o DELETE, mas esqueceu o WHERE (o erro da aula O Perigo do UPDATE e DELETE sem WHERE). Por sorte, ela estava dentro de uma transação:
BEGIN; DELETE FROM itens_pedido; -- faltou: WHERE pedido_id = 90 SELECT count(*) AS itens FROM itens_pedido; ROLLBACK; SELECT count(*) AS itens FROM itens_pedido;
- BEGIN;
- Abre a transação: dali em diante, nada é gravado de verdade.
- DELETE FROM itens_pedido;
- O acidente: sem
WHERE, apaga as 220 linhas da tabela. - SELECT count(*) …
- Dentro da transação: 0 itens. A Ana vê o estrago antes de gravar.
- ROLLBACK;
- "Volte atrás": desfaz tudo o que a transação fez e a fecha. Nada foi gravado.
- SELECT count(*) …
- Depois do
ROLLBACK: os 220 itens estão lá, intactos.
No quadro, o primeiro SELECT mostra 0 e o segundo mostra 220. O ROLLBACK não "apaga o DELETE" item por item: ele simplesmente joga fora a transação, e o banco continua exatamente como estava no BEGIN. Para os outros programas que usam o banco, nada aconteceu; eles nem chegaram a ver os itens sumirem.
No terminal, com o arquivo de verdade, é igual:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> BEGIN; sqlite> DELETE FROM itens_pedido; sqlite> SELECT count(*) FROM itens_pedido; 0 sqlite> ROLLBACK; sqlite> SELECT count(*) FROM itens_pedido; 220
2. A transação como ensaio
O ROLLBACK transforma qualquer mudança arriscada num ensaio: você faz, confere e só grava se gostou. A loja quer aumentar em 10% os preços dos eletrônicos (categoria 2), arredondando para duas casas com round() (veja Funções Numéricas). Antes de gravar, a Ana quer ver como ficam:
BEGIN; UPDATE produtos SET preco = round(preco * 1.10, 2) WHERE categoria_id = 2; SELECT nome, preco FROM produtos WHERE categoria_id = 2; ROLLBACK;
Ela olha a tabela, vê que a Caixa de Som passou de 189.99 para 208.99, e decide conversar com o gerente antes. Como terminou com ROLLBACK, nenhum preço mudou. Se tivesse gostado, trocaria o ROLLBACK por COMMIT.
Experimente: este ensaio terminou com COMMIT, e o aumento ficou gravado: os dois SELECT mostram o preço novo. Troque o comando certo para que o aumento seja só um ensaio, e o último SELECT mostre os preços antigos (o fone volta a 129.9).
BEGIN; UPDATE produtos SET preco = round(preco * 1.10, 2) WHERE categoria_id = 2; SELECT nome, preco FROM produtos WHERE categoria_id = 2; COMMIT; SELECT nome, preco FROM produtos WHERE categoria_id = 2;
BEGIN; UPDATE produtos SET preco = round(preco * 1.10, 2) WHERE categoria_id = 2; SELECT nome, preco FROM produtos WHERE categoria_id = 2; ROLLBACK; SELECT nome, preco FROM produtos WHERE categoria_id = 2;
Antes de rodar um UPDATE ou DELETE num banco de verdade, escreva BEGIN; primeiro. Rode o comando, confira o número de linhas afetadas com um SELECT e só então decida entre COMMIT e ROLLBACK. Se o UPDATE disser "45 linhas afetadas" quando você esperava 1, o ROLLBACK está a uma palavra de distância.
3. ROLLBACK depois de um erro
Na aula anterior, a Ana baixou o estoque, o INSERT do item falhou e ela deu COMMIT mesmo assim: o estoque ficou baixado sem a venda. O certo, depois de um erro, é desfazer tudo:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> BEGIN; sqlite> UPDATE produtos SET estoque = estoque - 2 WHERE id = 3; sqlite> INSERT INTO itens_pedido (pedido_id, produto_id, quantidade) VALUES (90, 3, 2); Runtime error: NOT NULL constraint failed: itens_pedido.preco_unitario (19) sqlite> ROLLBACK; sqlite> SELECT estoque FROM produtos WHERE id = 3; 430
O INSERT falhou porque faltou o preço. O ROLLBACK desfez também o UPDATE que tinha dado certo, e o estoque continua em 430. Agora a Ana corrige o INSERT e começa de novo, com BEGIN. Esse é o padrão de qualquer programa que usa banco de dados:
BEGIN.- Rode os comandos, um por um.
- Algum deu erro?
ROLLBACKe avise quem pediu a tarefa. - Todos deram certo?
COMMIT.
Nas linguagens de programação, esse padrão aparece como um try/catch ("tente"/"pegue o erro") em volta da transação. Você vai ver isso em SQL nas Linguagens.
4. Savepoints: desfazer só um pedaço
Às vezes, o erro está num pedaço da tarefa, e o resto está certo. A Rosângela Dias (cliente 30) está comprando três produtos com o vendedor Pedro Araújo (id 4). Os dois primeiros itens entram sem problema. O terceiro é o Pen Drive 32GB (produto 12), mas a Ana repara, ao conferir, que ele está descontinuado e com estoque 0: o estoque ficaria negativo. Ela quer desfazer só esse item e gravar o resto. Para isso, antes do item duvidoso, ela marca um savepoint:
BEGIN; INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete) VALUES (91, 30, 4, '2026-10-18', NULL, 0.0); INSERT INTO itens_pedido VALUES (91, 1, 2, 12.35); UPDATE produtos SET estoque = estoque - 2 WHERE id = 1; SAVEPOINT item2; INSERT INTO itens_pedido VALUES (91, 12, 1, 29.9); UPDATE produtos SET estoque = estoque - 1 WHERE id = 12; SELECT id, nome, estoque, descontinuado FROM produtos WHERE id IN (1, 12); ROLLBACK TO item2; RELEASE item2; COMMIT; SELECT pedido_id, produto_id, quantidade FROM itens_pedido WHERE pedido_id = 91; SELECT id, nome, estoque FROM produtos WHERE id IN (1, 12);
- SAVEPOINT item2;
- Marca um ponto chamado
item2no meio da transação. O nome é você quem escolhe, com as mesmas regras de um nome de tabela. - INSERT … / UPDATE …
- O item duvidoso. O
SELECTlogo depois mostra o problema: estoque -1 num produto descontinuado. - ROLLBACK TO item2;
- "Volte até
item2": desfaz só o que veio depois da marca. O pedido e o primeiro item continuam. A transação continua aberta, e a marca continua lá (dá para voltar a ela de novo). - RELEASE item2;
- "Solte
item2": tira a marca, que não é mais necessária. Não desfaz nem grava nada. - COMMIT;
- Grava o que sobrou: o pedido 91 com um item só, e o caderno com o estoque baixado.
No resultado, o primeiro SELECT (dentro da transação) mostra o pen drive com estoque -1. Depois do ROLLBACK TO e do COMMIT, o pedido 91 ficou com um item só (o caderno), o caderno foi de 120 para 118 e o pen drive voltou a 0. No quadro, o ROLLBACK TO aparece como "ROLLBACK: ok": para o SQLite, ele é uma forma do ROLLBACK.
| Comando | Desfaz | A transação | A marca |
|---|---|---|---|
ROLLBACK | Tudo, desde o BEGIN | Termina | Todas somem |
ROLLBACK TO nome | Só o que veio depois da marca | Continua aberta | Continua (e as marcas criadas depois dela somem) |
RELEASE nome | Nada | Continua aberta (se veio de um BEGIN) | Some, junto com as criadas depois dela |
COMMIT | Nada: grava tudo | Termina | Todas somem |
Várias marcas, uma dentro da outra
Dá para marcar quantos pontos quiser. Elas funcionam como uma pilha: voltar a uma marca antiga apaga as marcas mais novas que ela.
BEGIN; SAVEPOINT a; UPDATE produtos SET estoque = 1 WHERE id = 1; SAVEPOINT b; UPDATE produtos SET estoque = 2 WHERE id = 2; ROLLBACK TO a; RELEASE b;
O ROLLBACK TO a desfez os dois UPDATE e apagou a marca b, que tinha sido criada depois de a. Por isso o RELEASE b deu erro: "não existe o savepoint b".
SAVEPOINT sem BEGIN
No SQLite, um SAVEPOINT fora de qualquer transação abre uma transação, e o RELEASE dessa marca funciona como o COMMIT dela:
SAVEPOINT ajuste; UPDATE produtos SET estoque = 100 WHERE id = 1; RELEASE ajuste; SELECT nome, estoque FROM produtos WHERE id = 1;
Depois do RELEASE ajuste o estoque ficou gravado em 100, e não há mais transação aberta. É uma forma prática de escrever uma transação com nome, mas o BEGIN … COMMIT é mais claro e funciona em todos os bancos.
5. INSERT OR ROLLBACK: desfazer sozinho
Você viu que, no SQLite, um erro no meio desfaz só o comando que falhou. Mas dá para mandar um comando específico desfazer a transação inteira se ele violar uma regra da tabela (chave repetida, NOT NULL…): é o OR ROLLBACK, escrito logo depois do INSERT (ou do UPDATE):
sqlite> BEGIN; sqlite> UPDATE produtos SET estoque = estoque - 2 WHERE id = 3; sqlite> INSERT OR ROLLBACK INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario) VALUES (1, 1, 2, 12.35); Runtime error: UNIQUE constraint failed: itens_pedido.pedido_id, itens_pedido.produto_id (19) sqlite> SELECT estoque FROM produtos WHERE id = 3; 430 sqlite> COMMIT; Runtime error: cannot commit - no transaction is active
O pedido 1 já tinha o produto 1, e a chave (pedido_id, produto_id) não aceita repetição. Por causa do OR ROLLBACK, o SQLite desfez a transação toda: o estoque voltou a 430 sozinho. A prova é o COMMIT seguinte: "não há transação ativa". Útil, mas pouco comum; o jeito mais claro continua sendo testar o erro e dar ROLLBACK você mesmo.
6. Formas diferentes de fazer
| Situação | Use |
|---|---|
| Algo deu errado e nada da transação deve ficar | ROLLBACK; (ou ROLLBACK TRANSACTION;) |
| Ensaiar uma mudança e ver o resultado | BEGIN; … SELECT … ROLLBACK; |
| Desfazer só uma parte e continuar | SAVEPOINT x; … ROLLBACK TO x; |
| A parte deu certo, a marca não serve mais | RELEASE x; |
| Escrita longa, do padrão SQL | ROLLBACK TO SAVEPOINT x; e RELEASE SAVEPOINT x; (o mesmo que as formas curtas) |
| Um comando que, se falhar, deve desfazer tudo | INSERT OR ROLLBACK … / UPDATE OR ROLLBACK … |
Voltar ao estado de ontem, depois do COMMIT | Nenhum destes: só o backup (aula Backup) |
Até a estrutura do banco volta com o ROLLBACK. No SQLite, criar ou apagar uma tabela também faz parte da transação:
BEGIN; CREATE TABLE cupons (codigo TEXT, desconto REAL); ROLLBACK; SELECT * FROM cupons;
A tabela cupons foi criada dentro da transação e sumiu com o ROLLBACK: "não existe a tabela cupons". Isso não vale em todos os bancos (veja Nos outros bancos).
Quando dá errado
ROLLBACK sem transação
DELETE FROM itens_pedido WHERE pedido_id = 90; ROLLBACK;
O que aconteceu: "não posso voltar atrás: nenhuma transação está ativa". Sem BEGIN, o DELETE rodou no modo automático e foi gravado na hora. O ROLLBACK chegou tarde. No editor, como o banco é limpo a cada Rodar, isso não estraga nada; num arquivo de verdade, os itens do pedido 90 teriam sumido para sempre.
Como consertar: o BEGIN vem antes da mudança, nunca depois. Uma vez gravado, só o backup traz de volta.
ROLLBACK depois do COMMIT
BEGIN; DELETE FROM clientes WHERE id = 30; COMMIT; ROLLBACK;
O que aconteceu: a mesma mensagem. O COMMIT gravou e fechou a transação; não há mais nada a desfazer. O COMMIT é definitivo.
BEGIN; SAVEPOINT item2; UPDATE produtos SET estoque = estoque - 1 WHERE id = 12; ROLLBACK TO item_2;
O que aconteceu: "não existe o savepoint item_2". A marca se chama item2, sem o sublinhado. O RELEASE com um nome que não existe dá a mesma mensagem. Atenção: depois desse erro, a transação continua aberta e o UPDATE continua valendo.
Como consertar: use exatamente o nome do SAVEPOINT. (Maiúsculas e minúsculas não importam: ITEM2 também acha a marca item2.)
ROLLBACK TO encerra a transação
Depois de um ROLLBACK TO, a transação continua aberta. Se a Ana esquecer o COMMIT e fechar o sqlite3, perde tudo, inclusive a parte boa, que ela queria guardar. E, enquanto a transação estiver aberta, os outros programas recebem database is locked ao tentar escrever (veja Transações).
Como evitar: todo BEGIN termina em COMMIT ou ROLLBACK. O ROLLBACK TO não conta.
Nos outros bancos
O ROLLBACK é igual em todos. Os savepoints mudam um pouco (trechos ilustrativos, conforme a documentação oficial):
-- MySQL e PostgreSQL: como no SQLite (a palavra SAVEPOINT é opcional no PostgreSQL) SAVEPOINT item2; ROLLBACK TO SAVEPOINT item2; RELEASE SAVEPOINT item2; -- SQL Server: outro nome, e não existe RELEASE SAVE TRANSACTION item2; ROLLBACK TRANSACTION item2;
- PostgreSQL: o
SAVEPOINTsó funciona dentro de umBEGIN; ele não abre uma transação sozinho, como no SQLite. Como no SQLite, umCREATE TABLEpode ser desfeito peloROLLBACK. - MySQL:
CREATE TABLE,DROP TABLEeALTER TABLEfazem umCOMMITautomático da transação que estiver aberta. UmROLLBACKdepois deles não desfaz nem a tabela nem o que veio antes.
Referência rápida
| Comando | O que faz |
|---|---|
ROLLBACK; | Desfaz a transação inteira e a encerra |
SAVEPOINT nome; | Marca um ponto (fora de transação, abre uma) |
ROLLBACK TO nome; | Desfaz até a marca; a transação continua |
RELEASE nome; | Tira a marca (e as mais novas); se a marca abriu a transação, grava |
INSERT OR ROLLBACK … | Se o comando violar uma restrição, desfaz a transação toda |
| Mensagem | Quer dizer |
|---|---|
cannot rollback - no transaction is active | Não há transação aberta: a mudança já foi gravada (ou nunca houve BEGIN) |
no such savepoint: nome | Marca com nome errado, ou já apagada por um ROLLBACK TO/RELEASE anterior |
cannot commit - no transaction is active | Depois de um INSERT OR ROLLBACK que falhou: a transação já foi desfeita |
Exercícios
1. O ensaio
Complete para que a Ana veja o resultado do DELETE sem gravá-lo:
DELETE FROM itens_pedido WHERE pedido_id = 90;
SELECT count(*) FROM itens_pedido;
;
2. Voltar até a marca
Complete para desfazer só o que veio depois da marca frete:
UPDATE pedidos SET frete = 0 WHERE id = 90;
ROLLBACK frete;
3. Depois de BEGIN; DELETE FROM itens_pedido; ROLLBACK;, quantos itens tem a tabela?
4. Depois de um ROLLBACK TO item2, a transação…
5. A Ana rodou um UPDATE sem BEGIN e logo depois um ROLLBACK. O que acontece?
6. Para que serve o RELEASE nome?
Missão final: o item que não pode entrar
Missão: a venda do pedido 91 tem três itens, mas o terceiro, o Carrinho de Fricção (produto 27), está descontinuado e com estoque 0. Marque um SAVEPOINT antes do terceiro item e, depois dele, volte à marca com ROLLBACK TO (e solte-a com RELEASE), para que o pedido seja gravado só com os dois primeiros itens. O carrinho tem de continuar com estoque 0.
BEGIN; INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete) VALUES (91, 30, 4, '2026-10-18', NULL, 0.0); INSERT INTO itens_pedido VALUES (91, 30, 1, 21.75); UPDATE produtos SET estoque = estoque - 1 WHERE id = 30; INSERT INTO itens_pedido VALUES (91, 31, 2, 16.4); UPDATE produtos SET estoque = estoque - 2 WHERE id = 31; INSERT INTO itens_pedido VALUES (91, 27, 1, 18.6); UPDATE produtos SET estoque = estoque - 1 WHERE id = 27; COMMIT; SELECT id, nome, estoque FROM produtos WHERE id IN (27, 30, 31);
BEGIN; INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete) VALUES (91, 30, 4, '2026-10-18', NULL, 0.0); INSERT INTO itens_pedido VALUES (91, 30, 1, 21.75); UPDATE produtos SET estoque = estoque - 1 WHERE id = 30; INSERT INTO itens_pedido VALUES (91, 31, 2, 16.4); UPDATE produtos SET estoque = estoque - 2 WHERE id = 31; SAVEPOINT item3; INSERT INTO itens_pedido VALUES (91, 27, 1, 18.6); UPDATE produtos SET estoque = estoque - 1 WHERE id = 27; ROLLBACK TO item3; RELEASE item3; COMMIT; SELECT id, nome, estoque FROM produtos WHERE id IN (27, 30, 31);
Rode primeiro como está: o carrinho fica com estoque -1. Com a marca e o ROLLBACK TO, ele fica em 0, e a garrafa (94) e a corda (71) mantêm a baixa. Num programa de verdade, a decisão de voltar à marca viria de uma conferência ("o estoque ficou negativo?"), feita depois do UPDATE.
Resumo
ROLLBACKdesfaz a transação inteira, desde oBEGIN, e a encerra. Para os outros programas, nada aconteceu.- Use a transação como ensaio:
BEGIN, a mudança, umSELECTpara conferir e, só então,COMMITouROLLBACK. - Se um comando da transação falhar, dê
ROLLBACKe recomece; no SQLite o erro não desfaz o resto sozinho (a não ser comINSERT OR ROLLBACK). SAVEPOINT nomemarca um ponto;ROLLBACK TO nomedesfaz só o que veio depois (a transação continua);RELEASE nometira a marca.- Depois do
COMMIT(ou semBEGIN), não háROLLBACK:cannot rollback - no transaction is active. Aí, só o backup. - No SQLite, até
CREATE TABLEvolta com oROLLBACK; no MySQL, não.