Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Transações PostgreSQL no Node.js com segurança

Atualizado em: 17 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Transações PostgreSQL no Node.js garantem que um conjunto de operações seja confirmado por inteiro ou revertido. Elas são fundamentais para pagamentos, estoque, pedidos, contabilidade e qualquer regra que atualize múltiplas tabelas.

Uma transação correta precisa usar a mesma conexão do início ao fim, manter duração curta, tratar rollback e escolher isolamento adequado. Abrir BEGIN em uma conexão e executar queries em outra não oferece atomicidade.

Transação básica

const client = await pool.connect();
try {
  await client.query('BEGIN');
  await client.query(
    'UPDATE contas SET saldo = saldo - $1 WHERE id = $2',
    [valor, origemId]
  );
  await client.query(
    'UPDATE contas SET saldo = saldo + $1 WHERE id = $2',
    [valor, destinoId]
  );
  await client.query('COMMIT');
} catch (error) {
  await client.query('ROLLBACK');
  throw error;
} finally {
  client.release();
}

Helper transacional

export async function transaction(pool, callback) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const result = await callback(client);
    await client.query('COMMIT');
    return result;
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }
}

Repositories recebem client para garantir a mesma conexão.

Regra dentro da transação

await transaction(pool, async trx => {
  const pedido = await pedidos.obterPorId(pedidoId, trx);
  pedido.confirmar();
  await pedidos.salvar(pedido, trx);
  await outbox.adicionar(...pedido.removerEventos(), trx);
});

A mudança e a Outbox são atômicas.

ACID

  • Atomicidade: tudo ou nada;
  • Consistência: constraints e regras permanecem válidas;
  • Isolamento: transações concorrentes controlam visibilidade;
  • Durabilidade: commit confirmado sobrevive conforme configuração.

Níveis de isolamento

PostgreSQL oferece Read Committed, Repeatable Read e Serializable. Read Uncommitted se comporta como Read Committed.

BEGIN ISOLATION LEVEL SERIALIZABLE;

Isolamento maior reduz anomalias, mas pode aumentar aborts e necessidade de retry.

Read Committed

É o padrão. Cada comando vê dados confirmados antes do início daquele comando. Duas leituras na mesma transação podem observar valores diferentes.

Repeatable Read

A transação trabalha com snapshot consistente. Conflitos de atualização podem causar erro de serialização.

Serializable

Oferece comportamento equivalente a uma execução serial, mas algumas transações precisam ser repetidas.

Retry de serialização

async function withSerializableRetry(run, max = 3) {
  for (let attempt = 0; attempt < max; attempt++) {
    try {
      return await run();
    } catch (error) {
      if (error.code !== '40001' || attempt === max - 1) throw error;
      await new Promise(r => setTimeout(r, 20 * 2 ** attempt));
    }
  }
}

O callback precisa ser seguro para repetição e não executar efeitos externos irreversíveis dentro da tentativa.

Locks de linha

SELECT id, saldo
FROM contas
WHERE id = $1
FOR UPDATE;

O lock impede atualizações concorrentes na linha até o fim da transação.

Ordem de locks

Ao travar várias linhas, use ordem estável para reduzir deadlocks.

const ids = [origemId, destinoId].sort();

Deadlocks

PostgreSQL detecta deadlock e aborta uma transação. Trate o erro, registre contexto e repita apenas quando a operação for segura.

Savepoints

SAVEPOINT antes_opcional;
-- operação
ROLLBACK TO SAVEPOINT antes_opcional;

Savepoints permitem reverter parte da transação, mas não devem esconder fluxo confuso.

Constraints

Use constraints como última linha de defesa.

ALTER TABLE contas
ADD CONSTRAINT saldo_nao_negativo CHECK (saldo >= 0);

Validação apenas na aplicação está sujeita a concorrência.

Transações longas

Não aguarde usuário, fila ou API remota com a transação aberta. Locks longos aumentam contenção e impedem manutenção.

Efeitos externos

Não é possível incluir um e-mail ou chamada HTTP na transação PostgreSQL. Grave intenção em Outbox e execute depois.

Veja Outbox Pattern no Node.js.

Timeouts

Configure statement_timeout, lock_timeout e idle_in_transaction_session_timeout conforme o workload.

Observabilidade

Monitore duração, rollbacks, deadlocks, espera por locks, conexões ocupadas e transações ociosas.

Testes

  • commit completo;
  • rollback no segundo passo;
  • constraint violada;
  • duas operações concorrentes;
  • deadlock e retry;
  • serialização;
  • queda antes do commit;
  • Outbox gravada junto.

Erros comuns

  • queries usando conexões diferentes;
  • esquecer rollback;
  • transação longa;
  • chamada HTTP dentro do lock;
  • retry sem idempotência;
  • ignorar constraints;
  • não tratar deadlocks;
  • liberar conexão antes do commit.

Conclusão

Transações PostgreSQL no Node.js protegem invariantes locais quando são curtas, usam a mesma conexão e tratam concorrência. Escolha o isolamento com base nas anomalias que realmente precisam ser evitadas.

Combine constraints, locks, retries e Outbox. Leia também Connection Pooling no Node.js.

Consulte a documentação de transações do PostgreSQL e a documentação de transações do node-postgres.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita