Transações em Bancos de Dados
Published:
Transações são um dos mecanismos mais importantes dos bancos de dados relacionais: elas garantem que um conjunto de operações seja executado de forma segura, consistente e recuperável mesmo diante de falhas ou erros inesperados. Os exercícios a seguir foram elaborados para que você coloque esse conceito em prática em situações próximas do mundo real. Ambos podem ser realizados em qualquer um dos três bancos de dados relacionais mais utilizados (SQLite, PostgreSQL ou MySQL), e os scripts de preparação já incluem as adaptações necessárias para cada um deles. Escolha o ambiente com o qual você tem mais familiaridade, ou aproveite a oportunidade para experimentar um novo.
Exercício 1
Um banco digital mantém o saldo de seus clientes na tabela contas. Toda transferência entre contas deve ser atômica: ou o débito e o crédito acontecem juntos, ou nenhuma das duas operações é efetivada. Neste exercício você vai simular uma transferência e entender como o ROLLBACK protege a integridade dos dados.
Preparação do ambiente
Execute o script abaixo para criar e popular as tabelas antes de iniciar o exercício.
-- Criação da tabela
CREATE TABLE contas (
id INTEGER PRIMARY KEY,
titular TEXT NOT NULL,
saldo NUMERIC(12, 2) NOT NULL CHECK (saldo >= 0)
);
-- Dados iniciais
INSERT INTO contas (id, titular, saldo) VALUES
(1, 'Alice', 1500.00),
(2, 'Bob', 300.00);
Nota (SQLite): O SQLite não executa a constraint
CHECKem versões anteriores à 3.25. Nesses casos, a validação deverá ser feita manualmente na aplicação ou via trigger.
Tarefas
Parte A — Transferência bem-sucedida
- Abra uma transação explícita.
- Debite R$ 200,00 da conta de Alice (id = 1).
- Credite R$ 200,00 na conta de Bob (id = 2).
- Confirme a transação.
- Consulte as duas contas e verifique que os saldos foram atualizados corretamente.
Parte B — Transferência que deve ser cancelada
- Abra uma nova transação.
- Tente debitar R$ 5.000,00 da conta de Alice.
- Antes de confirmar, consulte o saldo de Alice dentro da transação e verifique que ele ficou negativo (ou que a constraint foi violada).
- Cancele a transação com
ROLLBACK. - Consulte novamente as contas fora da transação e confirme que nenhum dado foi alterado.
Perguntas para reflexão
- O que teria acontecido se o banco sofresse uma queda elétrica após o débito de Alice, mas antes do crédito de Bob, sem o uso de transação?
- Qual propriedade ACID garante que os dois
UPDATEs são tratados como uma única unidade de trabalho? - Por que consultar os dados dentro de uma transação não confirmada pode retornar valores diferentes do que outros usuários enxergam?
Exercício 2
Uma loja online registra seus produtos e movimentações de estoque em duas tabelas. Ao processar um pedido com múltiplos itens, cada baixa no estoque deve ser registrada individualmente. Se um item específico estiver indisponível, apenas a baixa daquele item deve ser desfeita — os demais itens já processados com sucesso devem ser mantidos. Para isso, usaremos SAVEPOINT.
Preparação do ambiente
-- Tabela de produtos
CREATE TABLE produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
estoque INTEGER NOT NULL CHECK (estoque >= 0)
);
-- Tabela de movimentações
CREATE TABLE movimentacoes (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- use SERIAL no PostgreSQL
produto_id INTEGER NOT NULL REFERENCES produtos(id),
quantidade INTEGER NOT NULL,
tipo TEXT NOT NULL, -- 'entrada' ou 'saida'
registrado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Dados iniciais
INSERT INTO produtos (id, nome, estoque) VALUES
(1, 'Notebook', 5),
(2, 'Mouse', 12),
(3, 'Teclado', 0); -- produto sem estoque!
Nota (PostgreSQL): Substitua
INTEGER PRIMARY KEY AUTOINCREMENTporSERIAL PRIMARY KEYna tabelamovimentacoes.
Tarefas
Um pedido solicita a retirada de: 2 Notebooks, 3 Mouses e 1 Teclado.
Parte A — Processamento item a item com SAVEPOINT
Para cada item do pedido, siga o roteiro abaixo dentro de uma única transação:
- Crie um
SAVEPOINTantes de tentar processar o item. - Atualize o estoque do produto (diminua a quantidade solicitada).
- Insira um registro na tabela
movimentacoescomtipo = 'saida'. - Verifique se o estoque resultante ficou negativo.
- Se ficou negativo: execute
ROLLBACK TO SAVEPOINTpara desfazer apenas aquele item e registre (em comentário ou em uma variável de controle) que o item falhou. - Se ficou válido: execute
RELEASE SAVEPOINTpara confirmar aquele ponto.
- Se ficou negativo: execute
- Ao final, confirme a transação com
COMMIT.
Parte B — Verificação dos resultados
Após o COMMIT, execute as consultas abaixo e registre os resultados obtidos:
-- Estoque atualizado
SELECT id, nome, estoque FROM produtos ORDER BY id;
-- Movimentações registradas
SELECT m.id, p.nome, m.quantidade, m.tipo, m.registrado_em
FROM movimentacoes m
JOIN produtos p ON p.id = m.produto_id
ORDER BY m.id;
Responda: quantas linhas aparecem em movimentacoes? Por quê?
Perguntas para reflexão
- Qual a diferença entre
ROLLBACK TO SAVEPOINTeROLLBACKcompleto? Em que cenários cada um é mais adequado? - Se você removesse os
SAVEPOINTs e mantivesse apenas umROLLBACKao detectar qualquer erro, qual seria o impacto para o pedido? - A propriedade ACID de isolamento garante que outros usuários não vejam as alterações parciais desta transação antes do
COMMIT. Como isso impacta a experiência de um usuário que consulta o estoque ao mesmo tempo que o pedido está sendo processado?
Referência rápida: comandos de transação
| Comando | Descrição |
BEGIN | Inicia uma transação explícita |
COMMIT | Confirma todas as alterações da transação |
ROLLBACK | Cancela todas as alterações desde o BEGIN |
SAVEPOINT <nome> | Cria um ponto de restauração dentro da transação |
ROLLBACK TO SAVEPOINT <nome> | Desfaz alterações até o savepoint, mantendo a transação aberta |
RELEASE SAVEPOINT <nome> | Remove o savepoint (sem desfazer nada) |
Dica: No PostgreSQL, qualquer erro dentro de uma transação coloca-a em estado de abortada. É necessário executar
ROLLBACK(ouROLLBACK TO SAVEPOINT) antes de continuar. No MySQL (com InnoDB) e no SQLite, o comportamento pode variar. Verifique a documentação da versão utilizada.
