SQL Transações: BEGIN e COMMIT

Registrar uma venda na Loja Aurora não é um comando só. É preciso criar o pedido, anotar cada item e tirar os produtos do estoque. Se a luz acabar entre o segundo e o terceiro passo, o banco fica com um pedido registrado e um estoque que não baixou. Para isso existem as transações: um jeito de dizer ao banco "estes comandos são uma coisa só; ou entram todos, ou não entra nenhum". Nesta aula, você vai abrir uma transação com BEGIN, gravá-la com COMMIT e ver o que outro programa enxerga enquanto ela está aberta.

Você vai aprender
  • O que é uma transação e a ideia do "tudo ou nada".
  • Que, sem você pedir, cada comando já é uma transação sozinho (o modo automático).
  • A abrir uma transação com BEGIN e gravar tudo de uma vez com COMMIT.
  • Que, dentro da transação, você vê as suas mudanças, mas os outros programas não.
  • O que é database is locked e por que ele aparece.
  • Por que mil INSERT numa transação ficam centenas de vezes mais rápidos.
  • As mensagens reais quando um BEGIN ou um COMMIT está sobrando, e por que um erro no meio não desfaz nada sozinho.

Os exemplos com quadro "Resultado" rodam no SQLite do seu navegador, com a Loja Aurora. Lembre que no editor cada Rodar começa com o banco limpo: a transação inteira (o BEGIN, os comandos e o COMMIT) precisa estar na mesma caixa. As partes com dois programas usando o banco ao mesmo tempo saíram do terminal da Ana, com o arquivo ~/loja.db.


A ideia: o carrinho do supermercado

No supermercado, você vai pondo as compras no carrinho. Enquanto não passa no caixa, nada é seu de verdade: você pode tirar coisas, desistir de tudo e ir embora. Quando paga, a compra inteira acontece de uma vez. Nunca acontece de metade do carrinho ser paga e a outra metade não.

No supermercadoNo banco
Pegar o carrinhoBEGIN: começa a transação
Pôr as compras no carrinhoOs INSERT, UPDATE e DELETE
Olhar o que está no carrinhoUm SELECT dentro da transação: você vê as suas mudanças
Pagar no caixaCOMMIT ("confirmar"): tudo é gravado de uma vez
Largar o carrinho e ir emboraROLLBACK ("voltar atrás"): nada é gravado (próxima aula, ROLLBACK e Savepoints)

Uma transação é esse pacote de comandos que o banco trata como uma coisa só. O nome vem do comércio: uma transação é uma troca completa, que acontece inteira ou não acontece.


1. Você já usa transações sem saber

Desde a aula de UPDATE, cada comando que você rodou já era uma transação: o SQLite abre uma, roda o comando e grava, sozinho. Isso se chama modo automático (em inglês, autocommit, "confirmação automática"). Por isso um UPDATE que mexe em 45 linhas nunca para na 20ª: ou muda as 45, ou nenhuma.

O problema aparece quando a tarefa tem vários comandos. No modo automático, cada um é gravado na hora, separado dos outros. Se o terceiro falhar, os dois primeiros já ficaram no banco. Para juntar vários comandos numa transação só, você mesmo a abre e a fecha.


2. BEGIN e COMMIT

O cliente Paulo Sérgio Andrade (id 27), que nunca tinha comprado na loja, levou 2 lápis (produto 3, R$ 1,15 cada), atendido pelo vendedor Caio Teles (id 2). A venda são três comandos, dentro de uma transação:

BEGIN;

INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete)
VALUES (91, 27, 2, '2026-10-18', NULL, 0.0);

INSERT INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario)
VALUES (91, 3, 2, 1.15);

UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;

COMMIT;

SELECT p.id, c.nome AS cliente, i.quantidade, pr.nome AS produto, pr.estoque
FROM pedidos AS p
JOIN clientes AS c ON c.id = p.cliente_id
JOIN itens_pedido AS i ON i.pedido_id = p.id
JOIN produtos AS pr ON pr.id = i.produto_id
WHERE p.id = 91;
BEGIN;
"Comece". Abre a transação. A partir daqui, nada é gravado de verdade até o COMMIT. (BEGIN TRANSACTION; é a mesma coisa, escrita por extenso.)
INSERT INTO pedidos …
Cria o pedido 91 (veja INSERT INTO).
INSERT INTO itens_pedido …
Anota o item: 2 unidades do produto 3, a 1.15.
UPDATE produtos …
Baixa o estoque do lápis de 430 para 428.
COMMIT;
"Confirme". Grava os três comandos de uma vez e fecha a transação.
SELECT …
Já fora da transação, confere a venda juntando as quatro tabelas (veja INNER JOIN).

No quadro, cada comando aparece com a sua resposta: "BEGIN: ok", "1 linha afetada" para cada INSERT e para o UPDATE, "COMMIT: ok" e, no fim, a tabela com a venda e o estoque já em 428.

Dentro da transação, você vê as suas mudanças

Antes do COMMIT, as mudanças ainda não estão gravadas. Mesmo assim, quem abriu a transação já as enxerga, como as compras no próprio carrinho:

SELECT estoque FROM produtos WHERE id = 3;

BEGIN;
UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
SELECT estoque FROM produtos WHERE id = 3;
COMMIT;

SELECT estoque FROM produtos WHERE id = 3;

O primeiro SELECT mostra 430. O segundo, ainda dentro da transação, já mostra 428: você vê o próprio UPDATE. O terceiro, depois do COMMIT, mostra 428 gravado. A diferença entre o segundo e o terceiro só aparece para outros programas, e é isso que a seção 4 mostra.

Experimente: registre outra venda numa transação. A cliente Tainá Soares (id 28) comprou 1 Mochila Escolar Azul (produto 5, R$ 89,99), atendida pela vendedora Renata Borges (id 3), em '2026-10-18', com frete de 15.0. O pedido já está escrito; falta o item e a baixa no estoque (que é de 35).

BEGIN;

INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete)
VALUES (91, 28, 3, '2026-10-18', NULL, 15.0);

-- falta o item do pedido 91 e a baixa no estoque do produto 5

COMMIT;

SELECT nome, estoque FROM produtos WHERE id = 5;
BEGIN;

INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete)
VALUES (91, 28, 3, '2026-10-18', NULL, 15.0);

INSERT INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario)
VALUES (91, 5, 1, 89.99);

UPDATE produtos SET estoque = estoque - 1 WHERE id = 5;

COMMIT;

SELECT nome, estoque FROM produtos WHERE id = 5;

3. Sem COMMIT, nada fica

No terminal, o banco é um arquivo que continua existindo depois que você sai. Veja o que acontece se a Ana abre uma transação, muda o estoque e sai do sqlite3 sem o COMMIT:

$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> SELECT estoque FROM produtos WHERE id = 3;
430
sqlite> BEGIN;
sqlite> UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
sqlite> SELECT estoque FROM produtos WHERE id = 3;
428
sqlite> .quit
$ sqlite3 loja.db "SELECT estoque FROM produtos WHERE id = 3;"
430                              « a mudança sumiu: faltou o COMMIT

Dentro da transação, o estoque já era 428. Mas a transação nunca foi confirmada, e uma transação que termina sem COMMIT (porque o programa fechou, travou ou a luz acabou) é desfeita pelo SQLite. Na próxima abertura, o estoque está em 430, como se nada tivesse acontecido. É exatamente o "tudo ou nada": a transação não terminou, então não aconteceu.


4. O que os outros programas enxergam

Um banco costuma ser usado por mais de um programa ao mesmo tempo: o caixa da loja registra vendas enquanto o sistema de estoque consulta os produtos. Para ver isso, a Ana abriu duas janelas de terminal, cada uma com o seu sqlite3, no mesmo loja.db. Na primeira, ela começa uma venda e não confirma ainda:

$ 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> SELECT estoque FROM produtos WHERE id = 3;
428

Com a transação do caixa ainda aberta, ela vai para a segunda janela e consulta o mesmo produto. Depois, tenta mudar o estoque:

$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> SELECT estoque FROM produtos WHERE id = 3;
430
sqlite> UPDATE produtos SET estoque = 0 WHERE id = 3;
Runtime error: database is locked (5)

Duas coisas aconteceram:

  • A segunda janela vê o estoque antigo, 430. A mudança do caixa ainda está "no carrinho": ninguém mais a enxerga até o COMMIT. Assim, ninguém toma decisões com dados que talvez nunca sejam gravados.
  • A segunda janela não consegue escrever: "erro ao executar: o banco está travado" (o 5 é o código de erro do SQLite para "travado"). No SQLite, só uma transação por vez pode escrever no banco. Enquanto a do caixa estiver aberta com mudanças, as outras esperam ou desistem.

Então o caixa confirma a venda:

sqlite> COMMIT;

E, na segunda janela, a mesma consulta agora mostra o valor novo:

sqlite> SELECT estoque FROM produtos WHERE id = 3;
428
MomentoTerminal 1 (caixa)Terminal 2 (estoque)
1BEGIN; e o UPDATE: vê 428
2(transação aberta)SELECT: vê 430, o valor gravado
3(transação aberta)UPDATE: database is locked
4COMMIT;
5SELECT: vê 428
Transação aberta é porta trancada

Uma transação esquecida aberta (alguém deu BEGIN, mudou uma linha e foi almoçar) trava a escrita de todos os outros programas. Abra a transação o mais tarde possível, faça o que precisa e dê o COMMIT logo. Nunca deixe uma transação esperando alguém digitar alguma coisa.

Se a espera for normal (um programa que só precisa aguardar alguns instantes), o sqlite3 pode ser mandado a tentar de novo por um tempo antes de desistir, com .timeout 5000 (5000 milissegundos = 5 segundos). Programas em Python, PHP e outras linguagens têm uma opção parecida, o busy timeout ("tempo de espera quando ocupado").


5. Transação deixa tudo mais rápido

Gravar de verdade no disco é a parte lenta de um banco: a cada COMMIT, o SQLite espera o disco garantir que os dados estão lá. No modo automático, isso acontece a cada comando. Numa transação, uma vez só, no fim.

A Ana montou um arquivo com mil INSERT numa tabela de teste, visitas, e uma segunda versão do mesmo arquivo com BEGIN; na primeira linha e COMMIT; na última. Depois, mediu o tempo de cada um com o time do terminal, em dois bancos novos:

$ head -2 visitas-transacao.sql
BEGIN;
INSERT INTO visitas VALUES (1, 'pagina-1');
$ tail -1 visitas-transacao.sql
COMMIT;
$ time sqlite3 sem.db < visitas.sql

real	0m9,944s                 « mil INSERTs, cada um gravado sozinho
user	0m0,041s
sys	0m0,234s
$ time sqlite3 com.db < visitas-transacao.sql

real	0m0,016s                 « os mesmos mil, numa transação só
user	0m0,000s
sys	0m0,004s
$ sqlite3 sem.db "SELECT count(*) FROM visitas;"
1000
$ sqlite3 com.db "SELECT count(*) FROM visitas;"
1000

Os dois bancos terminaram com as mesmas 1000 linhas. Sem transação, levou quase 10 segundos; com transação, 16 milésimos de segundo, umas seiscentas vezes mais rápido. O número exato muda de computador para computador (depende do disco), mas a diferença é sempre enorme. Foi por isso que o .dump da aula Backup veio com um BEGIN TRANSACTION no começo e um COMMIT no fim.


6. Formas diferentes de escrever

O SQLite aceita vários jeitos de abrir e fechar uma transação. Todos estes rodam:

BEGIN TRANSACTION;
UPDATE produtos SET descontinuado = 1 WHERE id = 43;
END TRANSACTION;

BEGIN IMMEDIATE;
UPDATE produtos SET descontinuado = 1 WHERE id = 44;
COMMIT TRANSACTION;

SELECT id, nome, descontinuado FROM produtos WHERE id IN (43, 44);
EscritaO que fazQuando usar
BEGIN; ou BEGIN TRANSACTION;Abre a transação. Só pede a vez de escrever no primeiro comando que muda alguma coisaO normal
BEGIN DEFERRED;O mesmo que BEGIN (deferred = "adiado"); é o padrãoRaramente escrito por extenso
BEGIN IMMEDIATE;Pede a vez de escrever já no BEGIN (immediate = "imediato"). Se outro programa estiver escrevendo, o erro database is locked vem logo no começo, e não no meio da tarefaQuando você sabe que vai escrever: o erro aparece antes de você ter feito qualquer coisa
BEGIN EXCLUSIVE;Parecido com o IMMEDIATE, ainda mais restrito (exclusive = "exclusivo")Casos especiais
COMMIT;, COMMIT TRANSACTION;, END;, END TRANSACTION;Todos confirmam e fecham a transaçãoPrefira COMMIT: é o que todo banco entende

Quando dá errado

COMMIT sem BEGIN
UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
COMMIT;

O que aconteceu: "não posso confirmar: nenhuma transação está ativa". O UPDATE rodou no modo automático e já foi gravado sozinho; o COMMIT não tinha nada para confirmar. É um erro inofensivo, mas mostra que o BEGIN ficou faltando, e com ele a proteção do "tudo ou nada". No terminal, a mensagem vem com o prefixo: Runtime error: cannot commit - no transaction is active.

Como consertar: comece com BEGIN; tudo o que precisa ir junto.

BEGIN dentro de BEGIN
BEGIN;
UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
BEGIN;

O que aconteceu: "não posso começar uma transação dentro de uma transação". No SQLite, as transações não se encaixam uma dentro da outra. Atenção: a primeira transação continua aberta, com o UPDATE pendente. Para "transações dentro de transações", existem os savepoints, da próxima aula.

Como consertar: feche a transação atual com COMMIT antes de abrir outra.

Um erro no meio não desfaz a transação

Este é o engano mais comum sobre transações. A Ana começa uma venda no terminal, baixa o estoque e esquece o preço do item:

$ 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> SELECT estoque FROM produtos WHERE id = 3;
428
sqlite> COMMIT;
sqlite> .quit
$ sqlite3 loja.db "SELECT estoque FROM produtos WHERE id = 3;"
428

O que aconteceu: o INSERT falhou ("a restrição NOT NULL falhou em itens_pedido.preco_unitario": faltou o preço, veja NOT NULL). Mas o SQLite desfez só aquele comando: a transação continuou aberta, com o UPDATE do estoque valendo. Quando a Ana deu COMMIT, gravou o estoque baixado sem o item da venda. Exatamente o que a transação devia evitar.

Como consertar: quando um comando da transação falhar, não dê COMMIT: desfaça tudo com ROLLBACK e comece de novo. É o assunto da próxima aula, ROLLBACK e Savepoints.

No editor, o mesmo erro para a execução ali, e o banco do editor é jogado fora no fim de cada Rodar. Por isso, no editor, nada do que veio antes do erro "fica"; no terminal, com um arquivo de verdade, fica:

BEGIN;
UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
INSERT INTO itens_pedido (pedido_id, produto_id, quantidade) VALUES (90, 3, 2);
COMMIT;
database is locked
sqlite> UPDATE produtos SET estoque = 0 WHERE id = 3;
Runtime error: database is locked (5)

O que aconteceu: outro programa (ou outra janela do terminal) está com uma transação de escrita aberta no mesmo banco, como na seção 4.

Como consertar: descubra quem está com a transação aberta e faça-o terminar (COMMIT ou ROLLBACK). Se for só uma espera curta e normal, use .timeout 5000 no sqlite3 (ou o busy timeout do seu programa). E mantenha as suas transações curtas.


Nos outros bancos

Transações no MySQL, PostgreSQL e SQL Server

Os três também trabalham no modo automático por padrão: sem você abrir uma transação, cada comando é confirmado sozinho. O jeito de abrir muda um pouco (trechos ilustrativos, conforme a documentação oficial):

-- SQL padrão; MySQL e PostgreSQL também aceitam BEGIN
START TRANSACTION;
UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
COMMIT;

-- SQL Server
BEGIN TRANSACTION;   -- ou BEGIN TRAN
UPDATE produtos SET estoque = estoque - 2 WHERE id = 3;
COMMIT TRANSACTION;

Uma diferença importante: no PostgreSQL, um erro dentro da transação a deixa "abortada", e todo comando seguinte é recusado até você encerrá-la; nem dá para dar o COMMIT do estoque sem o item, como a Ana fez acima. No SQLite, quem decide o que fazer depois do erro é você. E nos bancos com servidor, em geral, em vez de travar o banco inteiro para escrever, cada transação trava só as linhas que mudou, então duas vendas de produtos diferentes podem acontecer ao mesmo tempo.


Referência rápida

ComandoO que faz
BEGIN; / BEGIN TRANSACTION;Abre uma transação
BEGIN IMMEDIATE;Abre e já reserva a escrita
COMMIT; / END;Grava tudo e fecha a transação
ROLLBACK;Desfaz tudo e fecha a transação (próxima aula)
(sem BEGIN)Modo automático: cada comando é uma transação
.timeout 5000No sqlite3: espera até 5 s antes de dar database is locked
MensagemQuer dizer
cannot commit - no transaction is activeCOMMIT (ou END) sem transação aberta
cannot start a transaction within a transactionBEGIN com outra transação já aberta
database is locked (5)Outro programa está escrevendo no banco

Exercícios

1. Abrir e fechar

Complete para que as duas mudanças sejam gravadas juntas:

;
UPDATE produtos SET estoque = estoque - 1 WHERE id = 1;
UPDATE produtos SET estoque = estoque + 1 WHERE id = 2;
;

2. Sem nenhum BEGIN, a Ana roda um UPDATE. O que acontece?

3. No terminal, a Ana deu BEGIN, apagou um cliente e fechou o sqlite3 sem COMMIT. Ao abrir de novo, o cliente…

4. Com uma transação aberta no Terminal 1, que mudou o estoque de 430 para 428, o Terminal 2 consulta o estoque. O que ele vê?

5. Dentro de uma transação, o segundo de três comandos deu erro. No SQLite, o que acontece com o primeiro?

Missão final: a venda completa

Missão: registre numa transação só a venda do pedido 91: o cliente Paulo Sérgio Andrade (id 27) levou 3 Cadernos Pautados (produto 1, a 12.35) e 2 Canetas Azuis (produto 2, a 2.79), atendido por Caio Teles (id 2), em '2026-10-18', com frete 9.9. São cinco comandos entre o BEGIN e o COMMIT: o pedido, os dois itens e as duas baixas de estoque (o caderno tem 120, a caneta, 500).

BEGIN;

-- 1. o pedido 91
-- 2. os dois itens
-- 3. as duas baixas de estoque

COMMIT;

SELECT nome, estoque FROM produtos WHERE id IN (1, 2);
BEGIN;

INSERT INTO pedidos (id, cliente_id, funcionario_id, data, entregue_em, frete)
VALUES (91, 27, 2, '2026-10-18', NULL, 9.9);

INSERT INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario)
VALUES (91, 1, 3, 12.35),
       (91, 2, 2, 2.79);

UPDATE produtos SET estoque = estoque - 3 WHERE id = 1;
UPDATE produtos SET estoque = estoque - 2 WHERE id = 2;

COMMIT;

SELECT nome, estoque FROM produtos WHERE id IN (1, 2);

Os dois itens podem ir num INSERT só, com duas linhas de valores (veja Inserindo Várias Linhas), ou em dois INSERT separados: dentro da transação, dá no mesmo. O que importa é que, se qualquer um dos cinco falhasse, a Ana poderia desfazer tudo, e nenhum programa veria uma venda pela metade.


Resumo

  • Uma transação é um pacote de comandos que o banco trata como uma coisa só: tudo ou nada.
  • Sem BEGIN, cada comando é uma transação sozinho e é gravado na hora (modo automático, autocommit).
  • BEGIN; abre a transação; COMMIT; grava tudo de uma vez. Sem COMMIT (programa fechado, luz que acaba), a transação é desfeita.
  • Dentro da transação você vê as suas mudanças; os outros programas só as veem depois do COMMIT. Enquanto ela escreve, os outros recebem database is locked: mantenha as transações curtas.
  • Muitos INSERT numa transação ficam centenas de vezes mais rápidos: o disco só é esperado uma vez.
  • No SQLite, um erro no meio desfaz só o comando que falhou; a transação continua aberta. Não dê COMMIT depois de um erro: use ROLLBACK.