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.

Você vai aprender
  • A desfazer uma transação inteira com ROLLBACK.
  • A usar a transação como rede de proteção: testar um UPDATE ou DELETE perigoso, 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 com ROLLBACK TO e descartá-los com RELEASE.
  • O INSERT OR ROLLBACK, que desfaz tudo sozinho quando dá erro.
  • O que o ROLLBACK nã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 videogameNo banco
Começar uma partidaBEGIN
Desistir da partida e voltar ao menu, sem gravar nadaROLLBACK: desfaz tudo desde o BEGIN
Salvar antes da fase difícilSAVEPOINT fase2: marca um ponto
Carregar o jogo salvoROLLBACK TO fase2: desfaz só o que veio depois da marca
Apagar o jogo salvo, que não é mais precisoRELEASE fase2: tira a marca (sem desfazer nada)
Terminar a partida e gravar o recordeCOMMIT

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;
Um hábito que salva empregos

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:

  1. BEGIN.
  2. Rode os comandos, um por um.
  3. Algum deu erro? ROLLBACK e avise quem pediu a tarefa.
  4. 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 item2 no meio da transação. O nome é você quem escolhe, com as mesmas regras de um nome de tabela.
INSERT … / UPDATE …
O item duvidoso. O SELECT logo 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.

ComandoDesfazA transaçãoA marca
ROLLBACKTudo, desde o BEGINTerminaTodas somem
ROLLBACK TO nomeSó o que veio depois da marcaContinua abertaContinua (e as marcas criadas depois dela somem)
RELEASE nomeNadaContinua aberta (se veio de um BEGIN)Some, junto com as criadas depois dela
COMMITNada: grava tudoTerminaTodas 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çãoUse
Algo deu errado e nada da transação deve ficarROLLBACK; (ou ROLLBACK TRANSACTION;)
Ensaiar uma mudança e ver o resultadoBEGIN; … SELECT … ROLLBACK;
Desfazer só uma parte e continuarSAVEPOINT x; … ROLLBACK TO x;
A parte deu certo, a marca não serve maisRELEASE x;
Escrita longa, do padrão SQLROLLBACK TO SAVEPOINT x; e RELEASE SAVEPOINT x; (o mesmo que as formas curtas)
Um comando que, se falhar, deve desfazer tudoINSERT OR ROLLBACK … / UPDATE OR ROLLBACK …
Voltar ao estado de ontem, depois do COMMITNenhum 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.

Savepoint com nome errado
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.)

Achar que o 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

ROLLBACK e savepoints no MySQL, PostgreSQL e SQL Server

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 SAVEPOINT só funciona dentro de um BEGIN; ele não abre uma transação sozinho, como no SQLite. Como no SQLite, um CREATE TABLE pode ser desfeito pelo ROLLBACK.
  • MySQL: CREATE TABLE, DROP TABLE e ALTER TABLE fazem um COMMIT automático da transação que estiver aberta. Um ROLLBACK depois deles não desfaz nem a tabela nem o que veio antes.

Referência rápida

ComandoO 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
MensagemQuer dizer
cannot rollback - no transaction is activeNão há transação aberta: a mudança já foi gravada (ou nunca houve BEGIN)
no such savepoint: nomeMarca com nome errado, ou já apagada por um ROLLBACK TO/RELEASE anterior
cannot commit - no transaction is activeDepois 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:

SAVEPOINT 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

  • ROLLBACK desfaz a transação inteira, desde o BEGIN, e a encerra. Para os outros programas, nada aconteceu.
  • Use a transação como ensaio: BEGIN, a mudança, um SELECT para conferir e, só então, COMMIT ou ROLLBACK.
  • Se um comando da transação falhar, dê ROLLBACK e recomece; no SQLite o erro não desfaz o resto sozinho (a não ser com INSERT OR ROLLBACK).
  • SAVEPOINT nome marca um ponto; ROLLBACK TO nome desfaz só o que veio depois (a transação continua); RELEASE nome tira a marca.
  • Depois do COMMIT (ou sem BEGIN), não há ROLLBACK: cannot rollback - no transaction is active. Aí, só o backup.
  • No SQLite, até CREATE TABLE volta com o ROLLBACK; no MySQL, não.