Na resposta relacionada você está se referindo:
- Postgres UPDATE ... LIMIT 1
O objetivo é bloquear um linha de cada vez. Isso funciona bem com ou sem bloqueios consultivos, porque não há nenhuma chance de um impasse - desde que você não tente bloquear mais linhas na mesma transação.
Seu exemplo é diferente porque você deseja bloquear 3.000 linhas por vez . Há está potencial para deadlock, exceto se todas as operações de gravação simultâneas bloquearem linhas na mesma ordem consistente. Por documentação:
A melhor defesa contra deadlocks geralmente é evitá-los, tendo certeza de que todos os aplicativos que usam um banco de dados adquirem bloqueios em vários objetos em uma ordem consistente.
Implemente isso com um ORDER BY em sua subconsulta.
UPDATE cargo_item item
SET job_id = 'SOME_UUID', job_ts = now()
FROM (
SELECT id
FROM cargo_item
WHERE state='NEW' AND job_id is null
ORDER BY id
LIMIT 3000
FOR UPDATE
) sub
WHERE item.id = sub.id;
Isso é seguro e confiável, desde que todos as transações adquirem bloqueios na mesma ordem e não são esperadas atualizações simultâneas das colunas de ordenação. (Leia a caixa amarela "CUIDADO" no final deste capítulo no manual.) Portanto, isso deve ser seguro no seu caso, pois você não atualizará o
id
coluna. Efetivamente, apenas um cliente por vez pode manipular linhas dessa maneira. As transações simultâneas tentariam bloquear as mesmas linhas (bloqueadas) e aguardar a conclusão da primeira transação.
Bloqueios de aviso são úteis se você tiver muitas ou muito longas transações simultâneas (parece que você não tem). Com apenas alguns, será mais barato usar apenas a consulta acima e ter transações simultâneas aguardando sua vez.
Tudo em uma ATUALIZAÇÃO
Parece que o acesso simultâneo não é um problema em si na sua configuração. A simultaneidade é um problema criado por sua solução atual.
Em vez disso, faça tudo em um único
UPDATE
. Atribuir lotes de n
números (3000 no exemplo) para cada UUID e atualize tudo de uma vez. Deve ser mais rápido. UPDATE cargo_item c
SET job_id = u.uuid_col
, job_ts = now()
FROM (
SELECT row_number() OVER () AS rn, uuid_col
FROM uuid_tbl WHERE <some_criteria> -- or see below
) u
JOIN (
SELECT (row_number() OVER () / 3000) + 1 AS rn, item.id
FROM cargo_item
WHERE state = 'NEW' AND job_id IS NULL
FOR UPDATE -- just to be sure
) c2 USING (rn)
WHERE c2.item_id = c.item_id;
Pontos principais
-
A divisão inteira é truncada. Você obtém 1 para as primeiras 3.000 linhas, 2 para as próximas 3.000 linhas. etc.
-
Eu escolho linhas arbitrariamente, você pode aplicarORDER BY
na janela pararow_number()
para atribuir determinadas linhas.
-
Se você não tiver uma tabela de UUIDs para enviar (uuid_tbl
), use umVALUES
expressão para fornecê-los. Exemplo.
-
Você obtém lotes de 3.000 linhas. O último lote terá menos de 3.000 se você não encontrar um múltiplo de 3.000 para atribuir.