Abaixo estão as 3 estratégias mais comuns para coordenação de processamento concorrente em filas baseadas em banco de dados: SKIP LOCKED, WAIT e NOWAIT / erro imediato.
Cada instância pega apenas registros livres e ignora os que já estão travados por outra transação.
SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FETCH FIRST 100 ROWS ONLY
FOR UPDATE SKIP LOCKED;-- dentro de transação
SELECT id
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FETCH FIRST 100 ROWS ONLY
FOR UPDATE SKIP LOCKED;
-- depois processa e marca
UPDATE payment_request
SET status = 'PROCESSING',
updated_at = CURRENT_TIMESTAMP
WHERE id IN (...);Em Oracle, muitas vezes fica mais seguro escrever assim:
SELECT *
FROM (
SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
)
WHERE ROWNUM <= 100
FOR UPDATE SKIP LOCKED;- Instância A pega linhas
1–100 - Instância B pega linhas
101–200 - Instância C pega linhas
201–300
As linhas já bloqueadas por outras transações são simplesmente ignoradas.
- Filas de processamento
- Jobs distribuídos com múltiplas instâncias
- Cenários onde throughput e paralelismo importam
Aqui a instância tenta pegar as mesmas linhas, mas espera o lock ser liberado por alguns segundos.
SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FOR UPDATE WAIT 5;Isso significa: espere até 5 segundos se a linha já estiver bloqueada.
No PostgreSQL, o comportamento padrão de FOR UPDATE já é esperar indefinidamente:
BEGIN;
SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FOR UPDATE;
COMMIT;Se quiser limitar o tempo de espera:
SET LOCAL lock_timeout = '5s';
SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FOR UPDATE;- Instância A bloqueia linhas
- Instância B tenta pegar as mesmas linhas
- Instância B fica aguardando
- Quando A libera o lock, B continua
- Processamento serializado
- Quando você quer garantir espera ao invés de pular ou falhar
- Cenários em que conflito é aceitável e deve aguardar resolução
Aqui a instância tenta pegar o lock e, se ele já existir, recebe erro imediatamente.
SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FOR UPDATE NOWAIT;SELECT id, payload, status
FROM payment_request
WHERE status = 'PENDING'
ORDER BY id
FOR UPDATE NOWAIT;- Instância A bloqueia linhas
- Instância B tenta pegar as mesmas linhas
- O banco retorna erro imediatamente
- Oracle:
ORA-00054 - PostgreSQL: erro de lock não disponível
- Quando conflito deve falhar rápido
- Fluxos onde retry é tratado pela aplicação
- Cenários em que esperar não faz sentido
| Estratégia | Se a linha já está lockada |
|---|---|
FOR UPDATE SKIP LOCKED |
pula a linha e segue |
FOR UPDATE / WAIT |
espera o lock liberar |
FOR UPDATE NOWAIT |
lança erro imediatamente |
Suponha uma fila com linhas 1..10.
- Instância A pega
1,2,3 - Instância B pega
4,5,6 - Instância C pega
7,8,9
- Instância A pega
1,2,3 - Instância B tenta
1,2,3e espera - Instância C tenta
1,2,3e espera
- Instância A pega
1,2,3 - Instância B tenta
1,2,3e recebe erro - Instância C tenta
1,2,3e recebe erro
Na prática, o uso mais comum é:
SKIP LOCKED→ melhor para throughput e paralelismoWAIT→ melhor quando você quer serializaçãoNOWAIT→ melhor quando conflito deve falhar rápido
Se o seu caso é uma fila de processamento distribuída, normalmente a melhor estratégia é SKIP LOCKED, pois permite que múltiplas instâncias consumam lotes distintos sem sobreposição.
Se a intenção é esperar a liberação do lock, use WAIT.
Se a intenção é falhar imediatamente quando houver contenção, use NOWAIT.