Selecione linha desbloqueado no PostgreSQL

votos
32

Existe uma maneira para selecionar linhas no PostgreSQL que não estão bloqueados? Eu tenho um aplicativo multi-threaded que vai fazer:

Select... order by id desc limit 1 for update

sobre uma mesa.

Se vários segmentos executar essa consulta, ambos tentam puxar a mesma linha.

Tem-se a bloqueio de linha, os outros blocos e, em seguida, falhar após a primeira atualiza a linha. O que eu realmente gostaria é para o segundo segmento para obter a primeira linha que corresponda à WHEREcláusula e ainda não estiver bloqueado.

Para esclarecer, eu quero que cada thread para atualizar imediatamente a primeira linha disponível após fazer a seleção.

Assim, se houver linhas com ID: 1,2,3,4, o primeiro segmento viria em, selecione a linha com ID=4e imediatamente atualizá-lo.

Se durante essa operação um segundo thread vem, eu gostaria que ele para obter linha com ID=3e imediatamente atualizar essa linha.

Para Share não vai conseguir isso nem com nowaitcomo a WHEREcláusula irá coincidir com a linha bloqueada (ID=4 in my example). Basicamente o que eu gostaria é algo como E NÃO BLOQUEADO na WHEREcláusula.

Users

-----------------------------------------
ID        | Name       |      flags
-----------------------------------------
1         |  bob       |        0
2         |  fred      |        1
3         |  tom       |        0
4         |  ed        |        0

Se a consulta for Select ID from users where flags = 0 order by ID desc limit 1 e quando uma linha é devolvido o próximo passo é Update Users set flags = 1 where ID = 0, então eu gostaria que o primeiro segmento em agarrar a linha com ID 4ea próxima para pegar a linha com ID 3.

Se eu acrescentar For Update para o Select, em seguida, o primeiro segmento recebe a linha, o segundo um blocos e, em seguida, retorna nada porque uma vez que a primeira transação compromete a WHEREcláusula não é mais satisfeito.

Se eu não usar For Update, então eu preciso adicionar uma cláusula WHERE na atualização posterior (onde as bandeiras = 0) para que apenas um thread pode atualizar a linha.

O segundo segmento será selecionar a mesma linha como o primeiro, mas a atualização da segunda linha irá falhar.

De qualquer maneira o segundo segmento não consegue obter uma linha e atualização porque eu não posso obter o banco de dados para dar linha 4 para a primeira linha e linha 3 para o segundo segmento as transações se sobrepõem.

Publicado 23/12/2008 em 18:34
fonte usuário
Em outras línguas...                            


14 respostas

votos
0

Parece que você está procurando um SELECT FOR SHARE.

http://www.postgresql.org/docs/8.3/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE

PARA PARTILHAR comporta da mesma forma, exceto que ele adquire uma vez de bloqueio exclusivo compartilhado em cada linha recuperada. A blocos de bloqueio compartilhado outras transações executar UPDATE, DELETE ou SELECT FOR UPDATE nestas linhas, mas não impedi-los de realizar SELECT FOR SHARE.

Se tabelas específicas são nomeados em FOR UPDATE ou FOR SHARE, em seguida, apenas as linhas provenientes das mesas estão bloqueadas; quaisquer outras tabelas usadas no SELECT são simplesmente ler como de costume. A FOR UPDATE ou FOR cláusula da parte sem uma lista mesa afeta todas as tabelas usadas no comando. Se FOR UPDATE ou por participação é aplicado a uma vista ou sub-consulta, ele afeta todas as tabelas usadas na vista ou sub-consulta.

Multiple FOR UPDATE cláusulas compartilhamento pode ser escrito se é necessário especificar comportamento de bloqueio diferente para diferentes tabelas. Se a mesma tabela é mencionado (ou implicitamente afetado) por ambos FOR UPDATE cláusulas ACÇÕES, então ele é processado como FOR UPDATE. Do mesmo modo, uma tabela é processado como NOWAIT se que é especificada em qualquer das cláusulas que o afetam.

FOR UPDATE e para a parte não pode ser usado em contextos onde linhas retornadas não podem ser claramente identificados com linhas de tabela individuais; por exemplo, eles não podem ser usados ​​com agregação.

Respondeu 23/12/2008 em 18:50
fonte usuário

votos
1

Isso pode ser feito pelo comando SELECT ... NOWAIT; um exemplo é aqui .

Respondeu 23/12/2008 em 18:56
fonte usuário

votos
0

O que você está tentando realizar? você pode explicar melhor por que nem atualizações de linhas desbloqueados nem transações completos vai fazer o que você quer?

Melhor ainda, você pode evitar contenção e simplesmente ter cada thread usar um diferente compensar? Isso não vai funcionar bem se a parte relevante da tabela está sendo atualizado com freqüência; você ainda terá colisões, mas apenas durante o carregamento inserção pesado.

Select... order by id desc offset THREAD_NUMBER limit 1 for update
Respondeu 23/12/2008 em 19:00
fonte usuário

votos
0

Desde que eu não encontrei uma resposta melhor ainda, eu decidi usar o bloqueio dentro do meu aplicativo para sincronizar o acesso ao código que faz esta consulta.

Respondeu 23/12/2008 em 21:02
fonte usuário

votos
0

Como sobre o seguinte? Ela pode ser tratada com mais atomicamente do que os outros exemplos, mas deve ainda ser testado para se certificar de minhas hipóteses não estão errados.

UPDATE users SET flags = 1 WHERE id = ( SELECT id FROM users WHERE flags = 0 ORDER BY id DESC LIMIT 1 ) RETURNING ...;

Você provavelmente ainda vai ser preso com qualquer bloqueio postgres esquema usa internamente para fornecer resultados de SELECT consistentes em face de uma atualizações simultâneas.

Respondeu 24/12/2008 em 18:48
fonte usuário

votos
2

Parece que você está tentando fazer algo como pegar o maior item prioritário em uma fila que já não está sendo cuidado por outro processo.

Uma solução provável é adicionar uma cláusula onde limitando-o às solicitações não tratada:

select * from queue where flag=0 order by id desc for update;
update queue set flag=1 where id=:id;
--if you really want the lock:
select * from queue where id=:id for update;
...

Felizmente, a segunda transação irá bloquear enquanto a atualização para a bandeira acontece, então será capaz de continuar, mas a bandeira vai limitá-lo para o próximo na linha.

É também provável que a utilização do nível de isolamento serializável, você pode obter o resultado desejado sem toda essa insanidade.

Dependendo da natureza da sua aplicação, pode haver melhores formas de implementar isso do que no banco de dados, tais como um FIFO ou LIFO tubo. Além disso, pode ser possível inverter a ordem que precisa delas em, e utilizar uma sequência para garantir que eles são processados ​​sequencialmente.

Respondeu 03/02/2009 em 17:17
fonte usuário

votos
0

Eu enfrentei o mesmo problema em nossa aplicação e veio com uma solução que é muito semelhante ao Grant abordagem de Johnson. A FIFO ou LIFO tubo não era uma opção porque temos um cluster de servidores de aplicativos que acessam um DB. O que fazemos é um

SELECT ... WHERE FLAG=0 ... FOR UPDATE
imediatamente seguido por um
UPDATE ... SET FLAG=1 WHERE ID=:id
o mais cedo possível a fim de manter o tempo de bloqueio o mais baixo possível. Dependendo da contagem de coluna da tabela e tamanhos pode ajudar apenas buscar o ID no primeiro selecione e uma vez que você marcou a linha para buscar os dados restantes. Um procedimento armazenado pode reduzir a quantidade de round-trips ainda mais.

Respondeu 24/06/2009 em 21:41
fonte usuário

votos
0

^^ que funciona. considerar ter um status "imediata" de "bloqueado".

Vamos dizer que sua tabela é assim:

id | nomear | sobrenome | estado

E possíveis estados, por exemplo, são: 1 = pendente, 2 = bloqueado, 3 = processado, 4 = falhar, 5 = rejeitado

Cada novo registro é inserido com status pendente (1)

Seu programa faz: "set estado mytable update = 2, onde id = (select id de mytable onde nome like '% John%' e status = 1 limite de 1) retornando id, nome, sobrenome"

Em seguida, o programa faz sua coisa e se ele cames com a conclusão de que esta discussão não deve ter processado essa linha em tudo, ele faz: "atualização de status conjunto mytable = 1 onde id ="

Otherside ele atualiza a outros estados.

Respondeu 07/12/2009 em 13:40
fonte usuário

votos
7

Não Não NOOO :-)

Eu sei o que significa o autor. Eu tenho uma situação semelhante e eu vim com uma solução agradável. Primeiro vou começar de descrever a minha situação. Eu tenho uma tabela i que i armazenar mensagens que têm de ser enviados em um momento específico. O PG não suporta a execução de tempo de funções por isso temos de usar daemons (ou cron). Eu uso um script personalizado escrito que abre vários processos paralelos. Cada processo seleciona um conjunto de mensagens que devem ser enviadas com a precisão de 1 seg / -1 seg. A própria tabela é atualizada dinamicamente com novas mensagens.

Assim, cada processo precisa baixar um conjunto de linhas. Este conjunto de linhas não pode ser baixado por outro processo, porque ele vai fazer um monte de confusão (algumas pessoas receber mensagens de casal quando eles devem receber apenas uma). É por isso que temos de bloquear as linhas. A consulta para transferir um conjunto de mensagens com o bloqueio:

FOR messages in select * from public.messages where sendTime >= CURRENT_TIMESTAMP - '1 SECOND'::INTERVAL AND sendTime <= CURRENT_TIMESTAMP + '1 SECOND'::INTERVAL AND sent is FALSE FOR UPDATE LOOP
-- DO SMTH
END LOOP;

um processo com esta consulta é iniciada a cada 0,5 segundos. Então, isso vai resultar na próxima consulta esperando o primeiro bloqueio para desbloquear as linhas. Esta abordagem cria enormes atrasos. Mesmo quando usamos NOWAIT a consulta irá resultar em uma exceção que não queremos, porque pode haver novas mensagens na tabela que tem que ser enviada. Se o uso apenas para compartilhar a consulta irá executar corretamente, mas ainda vai demorar muito tempo criando enormes atrasos.

A fim de fazer o trabalho que fazemos um pouco de magia:

  1. mudando a consulta:

    FOR messages in select * from public.messages where sendTime >= CURRENT_TIMESTAMP - '1 SECOND'::INTERVAL AND sendTime <= CURRENT_TIMESTAMP + '1 SECOND'::INTERVAL AND sent is FALSE AND is_locked(msg_id) IS FALSE FOR SHARE LOOP
    -- DO SMTH
    END LOOP;
    
  2. a função misteriosa 'is_locked (msg_id)' se parece com isso:

    CREATE OR REPLACE FUNCTION is_locked(integer) RETURNS BOOLEAN AS $$
    DECLARE
        id integer;
        checkout_id integer;
        is_it boolean;
    BEGIN
        checkout_id := $1;
        is_it := FALSE;
    
        BEGIN
            -- we use FOR UPDATE to attempt a lock and NOWAIT to get the error immediately 
            id := msg_id FROM public.messages WHERE msg_id = checkout_id FOR UPDATE NOWAIT;
            EXCEPTION
                WHEN lock_not_available THEN
                    is_it := TRUE;
        END;
    
        RETURN is_it;
    
    END;
    $$ LANGUAGE 'plpgsql' VOLATILE COST 100;
    

Claro que pode personalizar esta função para trabalhar em qualquer tabela que você tem em seu banco de dados. Na minha opinião é melhor para criar uma função de verificação para uma tabela. Adicionando mais coisas para esta função pode fazer só mais lento. I leva mais tempo para verificar esta cláusula de qualquer maneira por isso não há necessidade de torná-lo ainda mais lento. Para mim, este a solução completa e ele funciona perfeitamente.

Agora quando eu tiver meus 50 processos em execução em paralelo cada processo tem um único conjunto de mensagens frescos para enviar. Uma vez que o são enviados eu só atualizar a linha com enviados = TRUE e nunca voltar a ele novamente.

Espero que esta solução também irá trabalhar para você (autor). Se você tem alguma dúvida é só me avisar :-)

Oh, e deixe-me saber se isso funcionou para você bem como,.

Respondeu 14/07/2010 em 03:52
fonte usuário

votos
6

Eu uso algo como isto:

select  *
into l_sms
from sms
where prefix_id = l_prefix_id
    and invoice_id is null
    and pg_try_advisory_lock(sms_id)
order by suffix
limit 1;

e não se esqueça de chamar pg_advisory_unlock

Respondeu 25/12/2010 em 15:02
fonte usuário

votos
4

Se você está tentando implementar uma fila, dê uma olhada PGQ, que tem resolvido este e outros problemas já. http://wiki.postgresql.org/wiki/PGQ_Tutorial

Respondeu 25/12/2010 em 19:28
fonte usuário

votos
0

Minha solução é usar a instrução UPDATE com a cláusula de retorno.

Users

-----------------------------------
ID        | Name       |      flags
-----------------------------------
1         |  bob       |        0  
2         |  fred      |        1  
3         |  tom       |        0   
4         |  ed        |        0   

Em vez de SELECT .. FOR UPDATEuso

BEGIN; 

UPDATE "Users"
SET ...
WHERE ...;
RETURNING ( column list );

COMMIT;

Porque a instrução UPDATE obtém um bloqueio ROW EXCLUSIVE em cima da mesa a sua actualização a obter atualizações serializados. Lê ainda são permitidos, mas eles só ver os dados antes do início da transação UPDATE.

Referência: Concorrência Controle Capítulo de docs Pg.

Respondeu 11/04/2011 em 05:24
fonte usuário

votos
0

Usado em multi-thread e cluster?
Que tal agora?

START TRANSACTION;

// All thread retrive same task list
// If result count is very big, using cursor 
//    or callback interface provied by ORM frameworks.
var ids = SELECT id FROM tableName WHERE k1=v1;

// Each thread get an unlocked recored to process.
for ( id in ids ) {
   var rec = SELECT ... FROM tableName WHERE id =#id# FOR UPDATE NOWAIT;
   if ( rec != null ) {
    ... // do something
   }
}

COMMIT;
Respondeu 17/11/2011 em 08:21
fonte usuário

votos
19

Este recurso, SELECT ... SKIP LOCKEDestá sendo implementado em Postgres 9.5. http://www.depesz.com/2014/10/10/waiting-for-9-5-implement-skip-locked-for-row-level-locks/

Respondeu 11/10/2014 em 00:19
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more