AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 69471
Accepted
vastlysuperiorman
vastlysuperiorman
Asked: 2014-07-02 08:54:16 +0800 CST2014-07-02 08:54:16 +0800 CST 2014-07-02 08:54:16 +0800 CST

Postgres UPDATE ... LIMIT 1

  • 772

Eu tenho um banco de dados Postgres que contém detalhes sobre clusters de servidores, como status do servidor ('ativo', 'em espera' etc). Servidores ativos a qualquer momento podem precisar fazer failover para um modo de espera, e não me importo com qual modo de espera é usado em particular.

Quero que uma consulta de banco de dados altere o status de um standby - APENAS UM - e retorne o IP do servidor que deve ser utilizado. A escolha pode ser arbitrária: como o status do servidor muda com a consulta, não importa qual standby 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;

Postgres não gosta disso. O que eu poderia fazer diferente?

postgresql update
  • 1 1 respostas
  • 131734 Views

1 respostas

  • Voted
  1. Best Answer
    Erwin Brandstetter
    2014-07-02T15:24:12+08:002014-07-02T15:24:12+08:00

    Sem acesso de gravação simultâneo

    Materialize uma seleção em um CTE (Common Table Expressions) e una-o na FROMcláusula do UPDATE.

    WITH cte AS (
       SELECT server_ip          -- pk column or any (set of) unique column(s)
       FROM   server_info
       WHERE  status = 'standby'
       LIMIT  1                  -- arbitrary pick (cheapest)
       )
    UPDATE server_info s
    SET    status = 'active' 
    FROM   cte
    WHERE  s.server_ip = cte.server_ip
    RETURNING s.server_ip;
    

    Eu originalmente tinha uma subconsulta simples aqui, mas isso pode evitar LIMITcertos planos de consulta, como Feike apontou:

    O planejador pode optar por gerar um plano que execute um loop aninhado sobre a LIMITingsubconsulta, causando mais UPDATEsde LIMIT, por exemplo:

     Update on buganalysis [...] rows=5
    
       ->  Nested Loop
             ->  Seq Scan on buganalysis
             ->  Subquery Scan on sub [...] loops=11
                   ->  Limit [...] rows=2
                         ->  LockRows
                               ->  Sort
                                     ->  Seq Scan on buganalysis
    

    Reproduzindo caso de teste

    A maneira de corrigir o acima foi envolver a LIMITsubconsulta em seu próprio CTE, pois o CTE é materializado, ele não retornará resultados diferentes em diferentes iterações do loop aninhado.

    Ou use uma subconsulta pouco correlacionada para o caso simples comLIMIT 1. Mais simples, mais rápido:

    UPDATE server_info
    SET    status = 'active' 
    WHERE  server_ip = (
             SELECT server_ip
             FROM   server_info
             WHERE  status = 'standby'
             LIMIT  1
             )
    RETURNING server_ip;
    

    Com acesso de gravação simultâneo

    Assumindo o nível de isolamento padrãoREAD COMMITTED para tudo isso. Níveis de isolamento mais rigorosos ( REPEATABLE READe SERIALIZABLE) ainda podem resultar em erros de serialização. Ver:

    • SELECT … FOR UPDATE SKIP LOCKED em transações REPEATABLE READ

    Sob carga de gravação simultânea, adicione FOR UPDATE SKIP LOCKEDpara bloquear a linha para evitar condições de corrida. SKIP LOCKEDfoi adicionado no Postgres 9.5 , para versões mais antigas veja abaixo. O manual:

    Com SKIP LOCKED, todas as linhas selecionadas que não podem ser bloqueadas imediatamente são ignoradas. Ignorar linhas bloqueadas fornece uma exibição inconsistente dos dados, portanto, isso não é adequado para trabalhos de uso geral, mas pode ser usado para evitar a contenção de bloqueios com vários consumidores acessando uma tabela semelhante a uma fila.

    UPDATE server_info
    SET    status = 'active' 
    WHERE  server_ip = (
             SELECT server_ip
             FROM   server_info
             WHERE  status = 'standby'
             LIMIT  1
             FOR    UPDATE SKIP LOCKED
             )
    RETURNING server_ip;

    Se não houver nenhuma linha qualificada e desbloqueada, nada acontece nesta consulta (nenhuma linha é atualizada) e você obtém um resultado vazio. Para operações não críticas, isso significa que você terminou.

    No entanto, transações simultâneas podem ter linhas bloqueadas, mas não concluem a atualização ( ROLLBACKou outros motivos). Para ter certeza , execute uma verificação final:

    SELECT NOT EXISTS (
       SELECT FROM server_info
       WHERE  status = 'standby'
       );
    

    SELECTtambém vê linhas bloqueadas. Wile que não retorna true, uma ou mais linhas ainda estão inacabadas e as transações ainda podem ser revertidas. (Ou novas linhas foram adicionadas enquanto isso.) Espere um pouco, então faça um loop nas duas etapas: ( UPDATEaté que você não receba nenhuma linha de volta; SELECT...) até que você obtenha true.

    Relacionado:

    • Atomic UPDATE .. SELECT no Postgres

    Sem SKIP LOCKEDno PostgreSQL 9.4 ou anterior

    UPDATE server_info
    SET    status = 'active' 
    WHERE  server_ip = (
             SELECT server_ip
             FROM   server_info
             WHERE  status = 'standby'
             LIMIT  1
             FOR    UPDATE
             )
    RETURNING server_ip;

    As transações simultâneas que tentam bloquear a mesma linha são bloqueadas até que a primeira libere seu bloqueio.

    Se a primeira foi revertida, a próxima transação pega o bloqueio e prossegue normalmente; outros na fila continuam esperando.

    Se o primeiro foi confirmado, a WHEREcondição é reavaliada e, se não for TRUEmais ( statusmudou), o CTE (surpreendentemente) não retornará 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 faz sentido esperar.

    Podemos desbloquear a situação com a ajuda de bloqueios consultivos :

    UPDATE server_info
    SET    status = 'active' 
    WHERE  server_ip = (
             SELECT server_ip
             FROM   server_info
             WHERE  status = 'standby'
             AND    pg_try_advisory_xact_lock(id)
             LIMIT  1
             FOR    UPDATE
             )
    RETURNING server_ip;

    Desta forma, a próxima linha ainda não bloqueada será atualizada. Cada transação recebe uma nova linha para trabalhar. Eu tive ajuda do Wiki Postgres tcheco para esse truque.

    idsendo qualquer bigintcoluna exclusiva (ou qualquer tipo com uma conversão implícita como int4ou int2).

    Se bloqueios consultivos estiverem em uso para várias tabelas em seu banco de dados simultaneamente, desambiguar com pg_try_advisory_xact_lock(tableoid::int, id)- idsendo exclusivo integeraqui.
    Como tableoidé uma bigintquantidade, teoricamente pode transbordar integer. Se você é paranóico o suficiente, use (tableoid::bigint % 2147483648)::int- deixando uma "colisão de hash" teórica para o verdadeiramente paranóico ...

    Além disso, o Postgres é livre para testar as WHEREcondições em qualquer ordem. Ele pode testar pg_try_advisory_xact_lock()e adquirir um bloqueio antes status = 'standby' de , o que pode resultar em bloqueios consultivos adicionais em linhas não relacionadas, onde status = 'standby'não é true. Pergunta relacionada no SO:

    • Postgres pg_try_advisory_lock bloqueia todos os registros

    Normalmente, você pode simplesmente ignorar isso. Para garantir que apenas as linhas qualificadas sejam bloqueadas, você pode aninhar o(s) predicado(s) em um CTE como acima ou uma subconsulta com o OFFSET 0hack (impede o inlining) . Exemplo:

    • Colocar pg_try_advisory_xact_lock() em uma subconsulta aninhada?

    Ou (mais barato para varreduras sequenciais) aninhe as condições em uma CASEdeclaração como:

    WHERE  CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END
    

    No entanto , o CASEtruque também impediria o Postgres de usar um índice no status. Se esse índice estiver disponível, você não precisará de aninhamento extra para começar: 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 apenas:

    WHERE  status = 'standby'
    AND    CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END
    

    O CASEé logicamente redundante, mas atende ao propósito discutido.

    Se o comando fizer parte de uma transação longa, considere os bloqueios em nível de sessão que podem ser (e devem ser) liberados manualmente. Assim, você pode desbloquear assim que terminar com a linha bloqueada: pg_try_advisory_lock()epg_advisory_unlock() . O manual:

    Uma vez adquirido no nível da sessão, um bloqueio consultivo é mantido até que seja liberado explicitamente ou a sessão termine.

    Relacionado:

    • Otimizando atualizações simultâneas no Postgres
    • 191

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve