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

Pessimistic Locking no Node.js: evite conflitos

Atualizado em: 17 de outubro de 2026

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

Pessimistic Locking no Node.js bloqueia registros durante uma transação para impedir que outras operações os alterem ao mesmo tempo. No PostgreSQL, comandos como SELECT ... FOR UPDATE são usados quando o conflito é provável e a regra precisa de exclusividade imediata.

O padrão é útil para estoque escasso, filas no banco, alocação de recursos e saldos concorridos. Porém, locks aumentam contenção e podem causar deadlocks. A transação deve ser curta e previsível.

Lock de linha

await transaction(pool, async client => {
  const result = await client.query(
    `SELECT id, quantidade
     FROM estoque
     WHERE sku = $1
     FOR UPDATE`,
    [sku]
  );

  const item = result.rows[0];
  if (!item || item.quantidade < quantidade) {
    throw new Error('Estoque insuficiente');
  }

  await client.query(
    `UPDATE estoque
     SET quantidade = quantidade - $1
     WHERE sku = $2`,
    [quantidade, sku]
  );
});

A linha fica bloqueada até commit ou rollback.

FOR NO KEY UPDATE

É uma forma mais fraca que FOR UPDATE e pode permitir mais concorrência quando chaves não serão alteradas. Escolha o modo conforme o conflito real.

FOR SHARE e FOR KEY SHARE

Esses modos protegem leituras contra determinadas alterações. São úteis em relacionamentos e validações de referências, mas exigem entendimento das compatibilidades de lock.

NOWAIT

SELECT * FROM recursos
WHERE id = $1
FOR UPDATE NOWAIT;

NOWAIT falha imediatamente se a linha estiver bloqueada. É adequado quando esperar não faz sentido.

SKIP LOCKED

SELECT id, payload
FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 20
FOR UPDATE SKIP LOCKED;

Vários workers podem selecionar trabalhos diferentes sem aguardar os mesmos registros.

Fila baseada em banco

await transaction(pool, async client => {
  const jobs = await client.query(`
    SELECT id, payload
    FROM jobs
    WHERE status = 'pending'
    ORDER BY created_at
    LIMIT 10
    FOR UPDATE SKIP LOCKED
  `);

  const ids = jobs.rows.map(job => job.id);
  await client.query(
    `UPDATE jobs
     SET status = 'processing', started_at = now()
     WHERE id = ANY($1::uuid[])`,
    [ids]
  );
});

O processamento longo deve ocorrer depois do commit. O lock serve apenas para reservar o lote.

Lock timeout

SET LOCAL lock_timeout = '2s';

Um timeout evita espera indefinida. Trate o erro como contenção, não como falha desconhecida.

Deadlocks

Dois processos podem travar recursos em ordem inversa. PostgreSQL detecta o ciclo e aborta uma transação.

Reduza deadlocks:

  • trave recursos sempre na mesma ordem;
  • mantenha transações curtas;
  • use índices para não bloquear linhas desnecessárias;
  • evite chamadas externas dentro da transação;
  • implemente retry limitado.

Ordem determinística

const accountIds = [originId, destinationId].sort();

for (const id of accountIds) {
  await client.query(
    'SELECT id FROM contas WHERE id = $1 FOR UPDATE',
    [id]
  );
}

Lock e pool

Todas as queries precisam usar a mesma conexão. Se um repository pegar outra conexão do pool, o lock não protege a operação esperada.

Transação curta

Não mantenha lock enquanto chama gateway, espera fila ou realiza trabalho de CPU. Reserve, atualize e confirme rapidamente. Efeitos externos podem ser coordenados com Outbox.

Pessimista ou otimista?

Use locking pessimista quando conflitos são frequentes e esperar é aceitável. Use otimista quando conflitos são raros e operações podem ser refeitas.

Leia Optimistic Locking no Node.js.

Advisory Locks

PostgreSQL oferece advisory locks para recursos que não correspondem diretamente a uma linha. Eles exigem disciplina, porque o banco não conhece a regra protegida.

SELECT pg_try_advisory_xact_lock($1);

Prefira locks transacionais para liberação automática no fim da transação.

Índices

Uma query sem índice pode examinar e bloquear mais linhas, aumentar duração e causar contenção. Analise o plano de execução.

Observabilidade

Monitore espera por locks, transações longas, deadlocks, lock timeouts, conexões ocupadas e rotas com maior contenção.

Testes concorrentes

  • dois workers disputando a mesma linha;
  • NOWAIT falhando;
  • SKIP LOCKED distribuindo jobs;
  • deadlock e retry;
  • rollback liberando lock;
  • timeout;
  • ordem estável de múltiplos recursos.

Erros comuns

  • lock fora de transação;
  • queries em conexões diferentes;
  • trabalho externo com lock aberto;
  • sem timeout;
  • ordem de locks inconsistente;
  • usar SKIP LOCKED onde todos os registros precisam ser vistos;
  • não medir contenção;
  • bloquear mais linhas por falta de índice.

Conclusão

Pessimistic Locking no Node.js oferece exclusividade imediata para regras concorridas. Ele funciona melhor com transações curtas, ordem determinística e timeouts.

Use o lock mais fraco que preserve a regra e evite chamadas externas durante a transação. Consulte Transações PostgreSQL no Node.js.

Leia a documentação de locking explícito do PostgreSQL.

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