Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Transações no PostgreSQL: COMMIT, ROLLBACK e locks

Proteja pedido e estoque com transações PostgreSQL, reproduza uma transação abortada, use savepoint e veja duas conexões disputarem a mesma linha.

Rodolfo Mori5 min de leitura

Uma transação agrupa vários comandos SQL numa operação de tudo ou nada. Nesta lição, pedido, item e estoque serão confirmados juntos, um erro será reproduzido e duas conexões vão disputar a mesma linha de propósito.

Os comandos principais são BEGIN, COMMIT e ROLLBACK. BEGIN abre o bloco, COMMIT torna as mudanças definitivas e ROLLBACK desfaz o que aconteceu desde o início. Sem BEGIN explícito, cada instrução roda numa transação implícita própria, o chamado modo autocommit.

Imagine o pedido num caixa de livraria. Baixar estoque, emitir o pedido e listar os itens são linhas do mesmo recibo. O recibo só entra no movimento do dia quando todas estão corretas; se uma falhar, ele é cancelado por inteiro. Esse é o mapa da atomicidade. O limite: conexões simultâneas não formam uma fila simples de caixa. O PostgreSQL usa versões de linhas, níveis de isolamento e locks para coordená-las.

Monte uma venda que realmente exige três escritas

O laboratório separa livros, pedidos e itens. A chave composta de itens_pedido impede repetir o mesmo livro no mesmo pedido. Execute num database vazio, separado das tabelas de produção:

sql
CREATE TABLE livros (
  id integer PRIMARY KEY,
  titulo text NOT NULL,
  estoque integer NOT NULL CHECK (estoque >= 0)
);

CREATE TABLE pedidos (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  status text NOT NULL DEFAULT 'criado',
  total numeric(10,2) NOT NULL CHECK (total > 0)
);

CREATE TABLE itens_pedido (
  pedido_id bigint REFERENCES pedidos(id) ON DELETE CASCADE,
  livro_id integer REFERENCES livros(id),
  quantidade integer NOT NULL CHECK (quantidade > 0),
  PRIMARY KEY (pedido_id, livro_id)
);

INSERT INTO livros (id, titulo, estoque)
VALUES (1, 'Dom Casmurro', 10),
       (2, 'A Hora da Estrela', 4);
CREATE TABLE CREATE TABLE CREATE TABLE INSERT 0 2

A transação não substitui as constraints. Ela coordena passos; as regras ainda decidem quais estados são válidos. A origem dessas chaves aparece em modelagem relacional.

COMMIT publica a venda inteira

A venda de dois exemplares de Dom Casmurro tem três comandos. O UPDATE só baixa se houver ao menos duas unidades, o pedido nasce com RETURNING e o item liga os dois:

sql
BEGIN;

UPDATE livros
SET estoque = estoque - 2
WHERE id = 1 AND estoque >= 2
RETURNING id, titulo, estoque;

INSERT INTO pedidos (total)
VALUES (79.80)
RETURNING id, status, total;

INSERT INTO itens_pedido (pedido_id, livro_id, quantidade)
VALUES (1, 1, 2);

COMMIT;
BEGIN id | titulo | estoque ----+--------------+--------- 1 | Dom Casmurro | 8 (1 row) UPDATE 1

id | status | total ––+––––+—–– 1 | criado | 79.80 (1 row) INSERT 0 1

INSERT 0 1 COMMIT

UPDATE 1 confirma que o filtro encontrou estoque. Se viesse UPDATE 0, a aplicação deveria fazer ROLLBACK e responder que a quantidade acabou, sem criar o pedido. Não basta o SQL terminar sem exceção; contagens também fazem parte da regra.

Depois do commit, uma nova consulta encontra as três partes da venda:

sql
SELECT l.titulo, l.estoque, p.id AS pedido, p.total
FROM livros AS l
JOIN itens_pedido AS i ON i.livro_id = l.id
JOIN pedidos AS p ON p.id = i.pedido_id
WHERE p.id = 1;
titulo | estoque | pedido | total --------------+---------+--------+------- Dom Casmurro | 8 | 1 | 79.80 (1 row)

Os joins no PostgreSQL apenas leem a relação; foi a transação que garantiu que ela não fosse publicada pela metade.

ROLLBACK é uma decisão, não apenas reação a erro

Você pode testar uma mudança dentro do bloco e desistir conscientemente. Nesta simulação, o estoque chega a um enquanto a transação está aberta:

sql
BEGIN;

UPDATE livros
SET estoque = estoque - 3
WHERE id = 2
RETURNING titulo, estoque;

ROLLBACK;
BEGIN titulo | estoque -------------------+--------- A Hora da Estrela | 1 (1 row) UPDATE 1 ROLLBACK

Após o rollback, a versão confirmada continua com quatro unidades:

sql
SELECT titulo, estoque
FROM livros
WHERE id = 2;
titulo | estoque -------------------+--------- A Hora da Estrela | 4 (1 row)

Isso também serve para manutenção manual: abra a transação, rode o UPDATE ou DELETE, confira contagens e amostras, e só então escolha COMMIT ou ROLLBACK. Não deixe a sessão aberta enquanto vai almoçar; ela pode reter versões e locks.

Depois de um erro, a transação fica abortada

Agora repetimos o item (pedido 1, livro 1), que já existe. Antes do erro, a mesma transação reduz o estoque:

sql
BEGIN;

UPDATE livros
SET estoque = estoque - 1
WHERE id = 1;

INSERT INTO itens_pedido (pedido_id, livro_id, quantidade)
VALUES (1, 1, 1);

SELECT estoque FROM livros WHERE id = 1;
ROLLBACK;

SELECT titulo, estoque FROM livros WHERE id = 1;
BEGIN UPDATE 1 ERROR: duplicate key value violates unique constraint "itens_pedido_pkey" DETAIL: Key (pedido_id, livro_id)=(1, 1) already exists. ERROR: current transaction is aborted, commands ignored until end of transaction block ROLLBACK titulo | estoque --------------+--------- Dom Casmurro | 8 (1 row)

O primeiro erro explica a regra violada. O segundo não é um novo defeito no SELECT: ele diz que o bloco já está abortado. O PostgreSQL ignora os comandos até receber ROLLBACK. Depois dele, a redução anterior também desaparece.

Em código de aplicação, todo caminho de falha precisa encerrar a transação. Um padrão seguro é: adquirir conexão, BEGIN, executar, COMMIT; no catch, ROLLBACK; e no finally, devolver a conexão ao pool. Liberar uma conexão abortada contamina a próxima operação que a receber.

SAVEPOINT desfaz um trecho sem descartar o bloco todo

Um savepoint marca uma posição intermediária. Aqui testamos um desconto e voltamos apenas ao valor anterior, mantendo a transação aberta para o commit:

sql
BEGIN;
SAVEPOINT antes_do_cupom;

UPDATE pedidos
SET total = total - 10
WHERE id = 1
RETURNING id, total;

ROLLBACK TO SAVEPOINT antes_do_cupom;
COMMIT;

SELECT id, total
FROM pedidos
WHERE id = 1;
BEGIN SAVEPOINT id | total ----+------- 1 | 69.80 (1 row) UPDATE 1 ROLLBACK COMMIT id | total ----+------- 1 | 79.80 (1 row)

O ROLLBACK exibido pelo psql corresponde ao ROLLBACK TO SAVEPOINT; ele não encerrou o bloco. Savepoint é útil quando uma parte opcional pode falhar e a operação principal ainda faz sentido. Ele não deve esconder um estado central inválido só para “salvar o resto”.

Isolamento define o que uma conexão enxerga

Atomicidade responde o que confirma junto. Isolamento responde quais mudanças simultâneas podem ser observadas. O padrão do PostgreSQL é read committed:

sql
SHOW transaction_isolation;
transaction_isolation ----------------------- read committed (1 row)

Nesse nível, cada instrução enxerga linhas confirmadas antes de começar. Duas consultas na mesma transação podem ver resultados diferentes se outra conexão fizer commit entre elas. REPEATABLE READ mantém um snapshot estável para o bloco; SERIALIZABLE também detecta combinações que não poderiam acontecer em execução uma por vez e pode abortar uma transação. Quanto mais forte a garantia, mais importante é tratar repetição após falhas de serialização.

FOR UPDATE reserva a linha até o fim do bloco

Abra dois terminais. No terminal A, selecione o livro para atualização e segure a transação por 30 segundos:

sql
BEGIN;
SELECT id, estoque
FROM livros
WHERE id = 1
FOR UPDATE;
SELECT pg_sleep(30);
COMMIT;
BEGIN id | estoque ----+--------- 1 | 8 (1 row)

– a sessão aguarda 30 segundos antes do COMMIT

Enquanto A segura o lock, rode no terminal B:

sql
SET lock_timeout = '500ms';
BEGIN;
UPDATE livros
SET estoque = estoque - 1
WHERE id = 1;
ROLLBACK;
SET BEGIN ERROR: canceling statement due to lock timeout CONTEXT: while updating tuple (0,3) in relation "livros" ROLLBACK

A atualização B precisa de um lock conflitante e espera. lock_timeout encerra a espera após meio segundo; a posição interna da tupla mostrada no CONTEXT pode mudar entre execuções. Uma leitura comum ainda pode ver a versão confirmada anterior pelo MVCC. FOR UPDATE deve cercar um trecho curto: selecione, valide, altere e confirme, sem chamada lenta de rede no meio.

Sua missão é simular uma venda de A Hora da Estrela: abra transação, bloqueie a linha com FOR UPDATE, diminua duas unidades, crie pedido e item e confirme. Depois repita pedindo dez unidades. O teste passa se a primeira operação deixar estoque dois e a segunda fizer rollback sem criar outro pedido.

Comandos corretos ainda dependem de tabelas bem desenhadas. A última etapa do cluster é modelagem relacional no PostgreSQL: ela transforma as regras que usamos aqui em chaves, cardinalidades e constraints antes que o sistema cresça.

  • postgresql
  • sql
  • transacao
  • commit
  • rollback
  • lock

Perguntas frequentes

Todo comando SQL já roda em uma transação?
Sim. Sem BEGIN explícito, o PostgreSQL executa cada instrução em uma transação própria e faz commit quando ela termina com sucesso. BEGIN agrupa várias instruções na mesma unidade.
Qual a diferença entre ROLLBACK e ROLLBACK TO SAVEPOINT?
ROLLBACK desfaz a transação inteira. ROLLBACK TO SAVEPOINT volta apenas ao ponto marcado e mantém o bloco aberto, permitindo corrigir ou seguir com outras operações antes do commit.
Por que aparece current transaction is aborted?
Um comando falhou dentro do bloco e a transação entrou em estado abortado. Os comandos seguintes são ignorados até ROLLBACK, ou até ROLLBACK TO de um savepoint criado antes do erro.
SELECT FOR UPDATE impede outras leituras?
Uma leitura comum continua vendo a versão confirmada conforme o isolamento. FOR UPDATE trava as linhas selecionadas contra atualizações e outros locks conflitantes até o fim da transação.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em PostgreSQL 18.6 em postgres:18-alpine e Docker 29.5.3, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. PostgreSQL 18 — Tutorial de transações — postgresql.org
  2. PostgreSQL 18 — BEGIN — postgresql.org
  3. PostgreSQL 18 — Isolamento de transações — postgresql.org
  4. PostgreSQL 18 — Locks explícitos — postgresql.org

Continue por aqui