O PostgreSQL possui um mecanismo muito conveniente para bloqueios consultivos , eles também são bloqueios consultivos . Nós da Tensor os usamos em muitos lugares do sistema, mas poucas pessoas entendem em detalhes como exatamente elas funcionam e quais problemas podem ser obtidos se mal tratados .
Leia mais sobre bloqueios Vantagens dos bloqueios de recomendação
A diferença fundamental entre esse mecanismo e os bloqueios "comuns" de tabela / página / nível de registro é que existem vários recursos principais.Bloqueio de ID personalizado
Bloqueios “normais” no PG estão sempre vinculados a um objeto de banco de dados específico (tabela, registro, página de dados) e ao processo que serve a conexão. Os bloqueios consultivos também são um processo, mas, em vez de um objeto real, um identificador abstrato pode ser definido como (bigint) ou como (número inteiro, número inteiro) .A propósito, anexar cada bloqueio a um processo significa que, nomeando-o pg_terminate_backend(pid)
ou completando corretamente a conexão no lado do cliente, você pode se livrar de todos os bloqueios impostos por ele.Verificação CAS para captura de bloqueio
O CAS é um Compare-and-Set , ou seja, a verificação da capacidade de captura e a captura da trava ocorrem como uma operação atômica e, obviamente, ninguém pode ficar entre elas.Ou seja, se você primeiro solicitar uma verificação para pg_locks , examinar o resultado e decidir se deve ou não bloquear, ninguém garante que, entre essas operações, ninguém conseguirá levar o objeto que você precisa. Mas se você usar pg_try_advisory_lock , receberá imediatamente esse bloqueio ou a função simplesmente retornará FALSE
.Não captura sem exceções e expectativas
Existem bloqueios “normais” no modelo “Se você já pediu um bloqueio, aguarde. Se você não quiser esperar ( NOWAIT
, statement_timeout
, lock_timeout
) - aqui é uma exceção " . Essa abordagem interfere bastante na transação, pois é necessário implementar um bloco BEGIN-EXCEPTION-END
para processar ou reverter ( ROLLBACK
) a transação.A única maneira de evitar esse comportamento é usar o design SELECT ... SKIP LOCKED
que acompanha a versão 9.5. Infelizmente, com esse método, as opções "não tinham o que bloquear" e "eram, mas já estão bloqueadas" tornam-se indistinguíveis.Os bloqueios recomendados causados pelas funções try simplesmente retornam TRUE/FALSE
.Não confunda pg_advisory_lock
e - a primeira função aguardará até receber um bloqueio, e a segunda simplesmente retornará imediatamente se for impossível capturá-lo "agora".pg_try_advisory_lock
FALSE
Bloqueios dentro e após a transação
Como mencionei acima, os bloqueios de objetos são "anexados" ao processo e existem apenas como parte da transação atual. Mesmo apenas para impor - não terá sucesso:LOCK TABLE tbl;
Assim, no final da transação, todos os bloqueios impostos a ela são removidos. Por outro lado, os bloqueios consultivos foram originalmente projetados com a capacidade de reter bloqueios e fora do escopo de uma transação :SELECT pg_advisory_lock(1);
SELECT * FROM pg_locks WHERE pid = pg_backend_pid() AND locktype = 'advisory';
-[ RECORD 1 ]------+--------------
locktype | advisory
database | 263911484
relation |
page |
tuple |
virtualxid |
transactionid |
classid | 0 <-- int4 #1 int8
objid | 1 <-- int4 #2 int8
objsubid | 1
virtualtransaction | 416/475768
pid | 29264
mode | ExclusiveLock
granted | t
fastpath | f
Mas já a partir da versão 9.1, surgiram versões xact de funções consultivas que permitem implementar o comportamento de bloqueios "comuns" que são liberados automaticamente quando a transação que os impôs é concluída.Exemplos de uso em VLSI
Na verdade, como qualquer outro bloqueio, os avisos servem para garantir a exclusividade do processamento de um recurso. Em nosso país, esses recursos geralmente são a tabela inteira ou um registro específico da tabela, que por algum motivo não deseja ser "bloqueado".Monoprocessamento do trabalhador
Se a necessidade de processar alguns dados no banco de dados for acionada por um evento externo, mas o processamento de vários processos for redundante ou pode levar a uma condição de corrida , é razoável possibilitar que apenas um processo trabalhe nesses dados por vez .Para fazer isso, tentaremos bloquear com o identificador da tabela como o primeiro parâmetro e o ID de processamento do aplicativo específico como o segundo:SELECT pg_try_advisory_lock(
'processed_table'::regclass::oid
, -1
);
Se retornarmos FALSE
, outra pessoa já está com esse bloqueio e, especificamente, esse processo não precisa fazer nada, mas é melhor terminar silenciosamente. É assim que, por exemplo, funciona o processo de cálculo dinâmico do custo das mercadorias em um depósito , isoladamente para cada esquema de cliente individual.Processamento de fila simultâneo
Agora a tarefa é "o oposto" - queremos que as tarefas em alguma tabela de filas sejam processadas o mais rápido possível, multithread, tolerante a falhas e até a partir de diferentes lógicas de negócios (bem, não temos o poder de uma) - por exemplo, como nosso operador faz sobre a transferência de relatórios eletrônicos para agências governamentais ou para o serviço OFD .Como os BLs de "processamento" são servidores diferentes, o mutex não pode mais ser desligado. Não é seguro destacar um coordenador de processos de distribuição de tarefas especial, "die" he - e tudo vai subir. E, portanto, é mais eficiente distribuir tarefas diretamente no nível do banco de dados, e esse método existe - nos tempos antigos, o modelo era honestamente espionado por Dmitry Koterov e modificado de forma criativa.Nesse caso, impomos um bloqueio na ID da tabela e na PK de um registro específico:SELECT
*
FROM
queue_table
WHERE
pg_try_advisory_lock('queue_table'::regclass::oid, pk_id)
ORDER BY
pk_id
LIMIT 1;
Ou seja, o processo receberá da tabela o primeiro registro que ainda não foi bloqueado pelos irmãos concorrentes.No entanto, se PK não consistir em (número inteiro) , mas em (número inteiro, número inteiro) (como no mesmo cálculo de custo, por exemplo), você pode impor um bloqueio diretamente nesse par - é improvável que ocorra uma interseção com o "concorrente".Importante! Não se esqueça de manter periodicamente adequadamente sua tabela de filas !Processamento exclusivo de documentos
É usado em qualquer lugar nas soluções de gerenciamento de documentos . De fato, em um sistema da Web distribuído, o mesmo documento pode ser aberto para visualização por diferentes usuários ao mesmo tempo, mas só pode ser processado (alterar seu estado etc.) a qualquer momento por apenas um.Questões tradicionais
Onde sem eles! Quase tudo se resume a uma coisa : eles não desbloquearam o que trancaram .Sobreposição múltipla de um bloqueio consultivo
RTFM , como se costuma dizer:Se várias solicitações de bloqueio forem recebidas de uma só vez, elas serão acumuladas. Portanto, se um recurso foi bloqueado três vezes, ele deve ser desbloqueado três vezes para estar disponível em outras sessões.
Coloque muitos bloqueios de uma só vez
Milhares deles! Leia o manual novamente :Os bloqueios consultivo e regular são armazenados na área de memória compartilhada, cujo tamanho é determinado pelos parâmetros de configuração max_locks_per_transaction
e max_connections
. É importante que essa memória seja suficiente, pois, caso contrário, o servidor não poderá emitir nenhum bloqueio . Portanto, o número de bloqueios recomendados que um servidor pode emitir geralmente é limitado a dezenas ou centenas de milhares, dependendo da configuração do servidor.
Em geral, se surgir uma situação em que você deseja impor vários milhares de bloqueios de aviso (mesmo que você os remova corretamente mais tarde) - pense muito bem onde será executado quando o servidor for ativado.Vazamentos ao filtrar registros
Aqui, pegamos a solicitação anterior e adicionamos uma condição inofensiva, como ID da verificação de paridade - AND pk_id % 2 = 0
. Ambas as condições para cada entrada serão verificadas ! Como resultado, foi pg_try_advisory_lock
concluído, o bloqueio foi sobreposto e o registro foi filtrado por paridade.
Ou uma opção do manual:SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345;
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100;
Isso é tudo - o bloqueio permanece , mas não estamos cientes disso. É tratado com os pedidos corretos, no pior dos casos - pg_advisory_unlock_all
.Oh, confuso!
Clássicos do gênero ...Confunda e , e me pergunto por que funciona por um longo tempo. Mas porque a versão sem tentativa está aguardando . Confunda e , e me pergunto para onde foi a fechadura - e "terminou" com a transação. E a transação consistiu em uma solicitação, porque em nenhum lugar foi declarada "explicitamente", sim.pg_try_advisory_lock
pg_advisory_lock
pg_try_advisory_lock
pg_try_advisory_xact_lock
Trabalhe através do pgbouncer
Essa é uma fonte separada de sofrimento para muitos quando, por uma questão de desempenho, trabalhar com o banco de dados passa pelo pgbouncer no modo de transação .Isso significa que duas de suas transações vizinhas em execução na mesma conexão com o banco de dados (que realmente passa pelo pgbouncer) podem ser executadas em diferentes conexões "físicas" no lado base. E eles têm seus próprios bloqueios ... Cada
um tem algumas opções:- ou trabalhar através de uma conexão direta com o banco de dados
- ou invente um algoritmo para que todos os bloqueios consultivos estejam apenas dentro da transação (xact)
É tudo por agora.