PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como marcar determinado número de linhas na tabela em acesso simultâneo


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 aplicar ORDER BY na janela para row_number() para atribuir determinadas linhas.

  • Se você não tiver uma tabela de UUIDs para enviar (uuid_tbl ), use um VALUES 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.