Eu tenho um banco de dados do Postgres que contém detalhes sobre grupos de servidores, como status do servidor ('ativo', 'em espera' etc.). Servidores ativos a qualquer momento podem precisar de failover para um modo de espera, e eu não me importo com qual modo de espera é usado em particular.
Desejo que uma consulta ao banco de dados altere o status de uma espera - APENAS UMA - e retorne o IP do servidor a ser usado. A escolha pode ser arbitrária: como o status do servidor muda com a consulta, não importa qual modo de espera está selecionado.
É possível limitar minha consulta a apenas uma atualização?
Aqui está o que eu tenho até agora:
UPDATE server_info SET status = 'active'
WHERE status = 'standby' [[LIMIT 1???]]
RETURNING server_ip;
O Postgres não gosta disso. O que eu poderia fazer de diferente?
postgresql
update
concurrency
queue
vastlysuperiorman
fonte
fonte
Respostas:
Sem acesso de gravação simultâneo
Materialize uma seleção em uma CTE e junte-se a ela na
FROM
cláusula doUPDATE
.Originalmente, eu tinha uma subconsulta simples aqui, mas isso pode contornar os
LIMIT
planos de consulta certos, como Feike apontou:Ou use uma subconsulta pouco correlacionada para o caso simples
LIMIT
1
. Mais simples, mais rápido:Com acesso de gravação simultâneo
Assumindo nível de isolamento padrão
READ COMMITTED
para tudo isso. Níveis de isolamento mais rigorosos (REPEATABLE READ
eSERIALIZABLE
) ainda podem resultar em erros de serialização. Vejo:Em carga de gravação simultânea, adicione
FOR UPDATE SKIP LOCKED
para bloquear a linha para evitar condições de corrida.SKIP LOCKED
foi adicionado no Postgres 9.5 , para versões mais antigas, veja abaixo. O manual:Se não houver nenhuma linha desbloqueada qualificada, nada acontece nesta consulta (nenhuma linha é atualizada) e você obtém um resultado vazio. Para operações não críticas, significa que você terminou.
No entanto, transações simultâneas podem ter linhas bloqueadas, mas não terminam a atualização (
ROLLBACK
ou outros motivos). Para ter certeza, execute uma verificação final:SELECT
também vê linhas bloqueadas. Enquanto isso não retornatrue
, uma ou mais linhas ainda estão sendo processadas e as transações ainda podem ser revertidas. (Ou novas linhas foram adicionadas enquanto isso.) Espere um pouco e, em seguida, execute os dois passos: (UPDATE
até que você não recupere nenhuma linha;SELECT
...) até obtertrue
.Palavras-chave:
Sem
SKIP LOCKED
no PostgreSQL 9.4 ou mais antigoAs transações simultâneas que tentam bloquear a mesma linha são bloqueadas até a primeira liberar seu bloqueio.
Se a primeira foi revertida, a próxima transação pega o bloqueio e continua normalmente; outros na fila continuam esperando.
Se o primeiro confirmado, a
WHERE
condição é reavaliada e, se nãoTRUE
houver mais (status
mudou), o CTE (de maneira surpreendente) não retorna nenhuma linha. Nada acontece. Esse é o comportamento desejado quando todas as transações desejam atualizar a mesma linha .Mas não quando cada transação deseja atualizar a próxima linha . E como queremos apenas atualizar uma linha arbitrária (ou aleatória ) , não há motivo para esperar.
Podemos desbloquear a situação com a ajuda de bloqueios consultivos :
Dessa forma, a próxima linha ainda não bloqueada será atualizada. Cada transação recebe uma nova linha para trabalhar. Eu tive a ajuda do Czech Postgres Wiki para esse truque.
id
sendo qualquerbigint
coluna exclusiva (ou qualquer tipo com uma conversão implícita comoint4
ouint2
).Se bloqueios consultivos estiverem em uso para várias tabelas no seu banco de dados simultaneamente, desambigue com
pg_try_advisory_xact_lock(tableoid::int, id)
-id
sendo um exclusivointeger
aqui.Como
tableoid
é umabigint
quantidade, pode teoricamente transbordarinteger
. Se você é paranóico o suficiente, use(tableoid::bigint % 2147483648)::int
- deixando uma "colisão de hash" teórica para o verdadeiro paranóico ...Além disso, o Postgres é livre para testar
WHERE
condições em qualquer ordem. Ele poderia testarpg_try_advisory_xact_lock()
e adquirir um bloqueio antesstatus = 'standby'
, o que poderia resultar em bloqueios consultivos adicionais em linhas não relacionadas, onde issostatus = 'standby'
não é verdade. Pergunta relacionada sobre SO:Normalmente, você pode simplesmente ignorar isso. Para garantir que apenas as linhas qualificadas estejam bloqueadas, você pode aninhar o (s) predicado (s) em um CTE como acima ou em uma subconsulta com o
OFFSET 0
hack (impede o inlining) . Exemplo:Ou (mais barato para verificações sequenciais) aninha as condições em uma
CASE
declaração como:No entanto, o
CASE
truque também impediria o Postgres de usar um índicestatus
. Se esse índice estiver disponível, você não precisará de aninhamento extra: apenas as linhas qualificadas serão bloqueadas em uma verificação de índice.Como você não pode ter certeza de que um índice é usado em todas as chamadas, você pode:
O
CASE
logicamente é redundante, mas ele serve ao objetivo discutido.Se o comando fizer parte de uma transação longa, considere bloqueios no nível da sessão que podem ser (e precisam ser) liberados manualmente. Assim, você pode desbloquear assim que terminar a linha bloqueada:
pg_try_advisory_lock()
epg_advisory_unlock()
. O manual:Palavras-chave:
fonte