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 / 80720
Accepted
arthur
arthur
Asked: 2014-10-22 01:46:14 +0800 CST2014-10-22 01:46:14 +0800 CST 2014-10-22 01:46:14 +0800 CST

Violação ACID no Postgres 9.1?

  • 772

Estou usando um banco de dados Postgres para implementar um agendamento de trabalhos para um grande número de computadores/processos. Para encurtar a história, cada trabalho tem seu id, todo o agendamento é implementado com três guias: todos os trabalhos, trabalhos em execução no momento e trabalhos já concluídos.

A principal funcionalidade do agendamento é (1) solicitar um trabalho e (2) informar o banco de dados sobre um trabalho concluído. A solicitação de um trabalho leva qualquer id da lista de trabalhos, que não esteja na tabela em execução e nem na tabela concluída:

insert into piper.jobs_running
select x.fid from ( 
  SELECT fid FROM piper.jobs
  except 
  select fid from piper.jobs_running
  except 
  select fid from piper.jobs_completed
 ) as x limit 1
returning(fid)

A conclusão do trabalho o remove da lista em execução e o insere na lista concluída. Como não é específico da simultaneidade, omito os comandos SQL (leva de dezenas de minutos a algumas horas para concluir um trabalho).

Foi uma surpresa desagradável para mim que dois processos executando exatamente a mesma consulta acima (praticamente solicitando o trabalho ao mesmo tempo) possam obter o mesmo id de trabalho (fid). A única explicação possível que estou apresentando é que o Postgres não é compatível com o ACID. Comentários?

Informações adicionais: Eu configurei as transações para serem serializáveis ​​(em postgresql.conf set default_transaction_isolation = 'serializable' ). Agora o SGBD falha nas transações caso o isolamento não seja cumprido. é possível forçar o Postgres a reiniciá-los automaticamente?

postgresql postgresql-9.1
  • 2 2 respostas
  • 759 Views

2 respostas

  • Voted
  1. Best Answer
    Craig Ringer
    2014-10-22T15:32:50+08:002014-10-22T15:32:50+08:00

    Problemas específicos com a consulta

    O padrão do PostgreSQL é o READ COMMITTEDisolamento e parece que você não está usando nada diferente. Em READ COMMITTEDcada instrução obtém seu próprio instantâneo. Ele não pode ver alterações não confirmadas de outras transações; é como se simplesmente não tivessem acontecido.

    Agora, imagine que você execute isso simultaneamente em três sessões, em uma configuração com três entradas em jobs, zero em jobs_runninge zero em jobs_completed:

    insert into piper.jobs_running
    select x.fid from ( 
      SELECT fid FROM piper.jobs
      except 
      select fid from piper.jobs_running
      except 
      select fid from piper.jobs_completed
     ) as x limit 1
    returning(fid)
    

    Cada execução selecionará os três trabalhos em jobs. Como seus instantâneos foram todos tirados antes de qualquer um deles fazer uma alteração ou até mesmo criar a linha não confirmada, *todos eles encontrarão zero linhas em jobs_runninge jobs_completed.

    Então todos eles reivindicam um emprego. Provavelmente o mesmo trabalho , porque mesmo que não haja ORDER BY, a ordem de digitalização será a mesma.

    Bloqueio

    O bloqueio de linha cruza as fronteiras transnacionais e permite que você se comunique entre as transações para impor a ordem. Então você pode pensar que isso resolveria seu problema, mas não vai.

    Se você FOR UPDATEbloquear a rowentrada, de modo que a linha seja bloqueada exclusivamente, o bloqueio será retido até que a transação seja confirmada ou revertida. Portanto, você pensaria que a próxima transação obteria uma linha diferente ou, se tentasse obter a mesma linha, aguardaria a liberação do bloqueio, verificaria se agora havia uma entrada em jobs_runninge pularia a linha. Você estaria errado.

    O que acontecerá é que você bloqueará todas as linhas. Uma transação obterá com sucesso os bloqueios em todas as linhas. As outras transações, que farão o mesmo índice ou varredura sequencial, geralmente tentarão bloquear as linhas na mesma ordem, travarão na primeira tentativa de bloqueio e aguardarão a reversão ou confirmação da primeira transação. Se você não tiver sorte, eles podem começar a bloquear diferentes conjuntos de linhas e travar uns contra os outros, causando a interrupção do impasse, mas geralmente você não está obtendo nenhuma simultaneidade útil.

    Pior ainda, a primeira transação escolhe uma das linhas que bloqueou, insere uma linha jobs_runninge confirma, liberando os bloqueios. Outra transação é capaz de continuar e bloqueia todas as linhas .... mas não obtém um novo instantâneo do estado do banco de dados (o instantâneo é obtido no início da instrução), então * não pode ver que você inseriu uma linha em jobs_running. Assim, ele pega o mesmo trabalho, insere uma linha para esse trabalho em jobs_runninge confirma.

    Novas verificações de condição

    O PostgreSQL tem um recurso peculiar não incluído na maioria dos bancos de dados onde, se uma transação for bloqueada em um bloqueio, ele verifica novamente se a linha selecionada ainda corresponde à condição de bloqueio quando obtém o bloqueio após a confirmação da primeira transação.

    É por isso que o exemplo em https://stackoverflow.com/questions/11532550/atomic-update-select-in-postgres funciona - ele se baseia em novas verificações de qualificador em WHEREcláusulas após recuperar um bloqueio.

    O uso de locking força tudo a rodar serialmente, então na prática você pode muito bem ter uma única conexão fazendo o trabalho.

    Isolamento, ACID e realidade

    O isolamento de transações no PostgreSQL não é o ideal perfeito onde as transações são executadas simultaneamente, mas seus efeitos são os mesmos como se fossem executados em série.

    O único banco de dados do mundo real que forneceria isolamento perfeito seria aquele que bloqueia exclusivamente todas as tabelas quando uma transação de gravação a acessa pela primeira vez; portanto, na prática, as transações só podem ser de acesso simultâneo se forem para diferentes partes do banco de dados. Ninguém deseja usar esse banco de dados em situações em que a simultaneidade é desejável ou útil.

    Todas as implementações do mundo real são concessões.

    READ COMMITTEDpredefinição

    O padrão do PostgreSQL é o READ COMMITTEDisolamento, um nível de isolamento bem definido que permite leituras não repetíveis e fantasmas , conforme discutido no manual do PostgreSQL sobre isolamento de transação .

    SERIALIZABLEisolamento

    Você pode solicitar um SERIALIZABLEisolamento mais rígido por transação ou como padrão global por usuário, por banco de dados ou (não recomendado). Isso fornece garantias muito mais fortes, embora ainda não perfeitas, ao custo de forçar a reversão das transações se elas interagirem de maneiras que não poderiam acontecer se tivessem sido executadas em série.

    Como suas consultas simultâneas sempre tentarão obter o primeiro trabalho, não importa quantos trabalhos existam, todos, exceto um, serão interrompidos com falhas de serialização. Portanto, na prática, você não obtém nenhuma simultaneidade útil e pode muito bem ter uma única conexão distribuindo tarefas aos trabalhadores.

    (Observe que antes do PostgreSQL 9.1 SERIALIZABLEoferecia garantias muito mais fracas e não detectava muitos casos em que as transações eram interdependentes.)

    Reexecução automáticaSERIALIZABLE

    O PostgreSQL não executa automaticamente transações SERIALIZABLEque abortam devido a falhas de serialização. Há casos em que isso seria bastante útil, mas outros casos em que isso seria completamente errado e perigoso - especialmente quando estão envolvidos ciclos de leitura/modificação/gravação por meio do aplicativo. No momento, não há suporte para a reexecução automática de transações em falhas de serialização. Espera-se que o aplicativo tente novamente.

    Não escreva um sistema de filas DIY

    Parece que o que você está tentando fazer é escrever um sistema de filas. Considere não fazer isso. Escrever um sistema de filas robusto, confiável e correto é difícil, e existem alguns bons já disponíveis que você pode adotar. Você tem que lidar com coisas como segurança contra falhas, o que acontece quando alguém pega uma tarefa e não consegue completá-la, condições de corrida em torno da conclusão assim que você desiste de o manipulador de tarefas concluí-la, etc. Existem muitos problemas sutis de simultaneidade. Não tente DIY.

    9.5 eSKIP LOCKED

    O PostgreSQL 9.5, ainda em desenvolvimento, adiciona um recurso que torna o enfileiramento um pouco mais fácil.

    Ele permite que você diga que if when you SELECT ... FOR UPDATE, se uma linha estiver bloqueada, você deve ignorá-la e continuar para encontrar a próxima linha não bloqueada. Isso é muito útil quando combinado com um LIMIT, pois permite dizer "encontre-me a primeira linha que outra pessoa ainda não está tentando reivindicar". Portanto, torna-se muito simples escrever filas que capturam trabalhos de forma segura e simultânea.

    Até que esse recurso esteja disponível, sugiro fortemente que você se atenha ao gerenciamento de fila de conexão única ou use um sistema de fila de tarefas bem testado.

    • 6
  2. arthur
    2014-10-24T02:48:14+08:002014-10-24T02:48:14+08:00

    A resposta de craig-ringer responde totalmente à pergunta. Por uma questão de integridade, adiciono o seguinte código trivial para resolver o problema usando bloqueios:

    create or replace function f_requestJob(
        jobs           text, 
        jobs_running   text, 
        jobs_completed text
    ) 
        returns bigint as
    $$
    declare 
        sql varchar;
        c bigint;
    
    begin
        execute 'LOCK TABLE ' || jobs           || ' IN ACCESS EXCLUSIVE MODE'; 
        execute 'LOCK TABLE ' || jobs_running   || ' IN ACCESS EXCLUSIVE MODE'; 
        execute 'LOCK TABLE ' || jobs_completed || ' IN ACCESS EXCLUSIVE MODE';
    
        sql := '
        insert into ' || jobs_running || '
        select x.fid     
        from ( 
          SELECT fid FROM ' || jobs || '
          except 
          select fid from ' || jobs_running || '
          except 
          select fid from ' || jobs_completed || '
         ) as x limit 1
        returning(fid)';
    
        execute sql into c;
    
        return c; --may be a null value
    
    end;
    $$ language plpgsql;
    
    • 0

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