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 / 212580
Accepted
Elliot Blackburn
Elliot Blackburn
Asked: 2018-07-19 08:03:16 +0800 CST2018-07-19 08:03:16 +0800 CST 2018-07-19 08:03:16 +0800 CST

Transações simultâneas resultam em condição de corrida com restrição exclusiva na inserção

  • 772

Eu tenho um serviço da web (http api) que permite que um usuário crie um recurso tranquilamente. Após a autenticação e validação eu passo os dados para uma função do Postgres e permito que ela verifique a autorização e crie os registros no banco de dados.

Encontrei um bug hoje quando duas solicitações http foram feitas no mesmo segundo, o que fez com que essa função fosse chamada com dados idênticos duas vezes. Existe uma cláusula dentro da função que faz um select em uma tabela para ver se existe um valor, se existe eu pego o ID e uso isso na minha próxima operação, se não existir eu insiro os dados, obtenho de volta o ID e, em seguida, use-o na próxima operação. Abaixo está um exemplo simples.

select id into articleId from articles where title = 'my new blog';
if articleId is null then
    insert into articles (title, content) values (_title, _content)
    returning id into articleId;
end if;
-- Continue, using articleId to represent the article for next operations...

Como você provavelmente pode adivinhar, obtive uma leitura fantasma nos dados em que ambas as transações entraram no if articleId is null thenbloco e tentaram inserir na tabela. Um teve sucesso e o outro explodiu por causa de uma restrição única em um campo.

Eu dei uma olhada em como me defender contra isso e encontrei algumas opções diferentes, mas nenhuma parece atender às nossas necessidades por alguns motivos e estou lutando para encontrar alternativas.

  1. insert ... on conflict do nothing/update...Eu olhei primeiro para a on conflictopção que parecia boa, no entanto, a única opção é para do nothingqual não retorna o ID do registro que causou a colisão e do updatenão funcionará, pois fará com que os gatilhos sejam disparados quando, na realidade, os dados não mudou. Em alguns casos, isso não é um problema, mas em muitos casos isso pode invalidar as sessões do usuário, o que não é algo que podemos fazer.
  2. set transaction isolation level serializable;esta parece ser a resposta mais atraente, no entanto, mesmo nosso conjunto de testes pode causar dependências de leitura/gravação onde, como acima, queremos inserir se algo não existir e devolvê-lo se existir e continuar com outras operações. Se tivermos várias transações pendentes que executam o código acima, isso causará um erro de dependência de leitura/gravação, conforme descrito na transação-iso dos documentos do Postgres .

Como esse tipo de transação de leitura/gravação simultânea deve ser tratada?

Nem eu nem minha equipe afirmamos ser especialistas em banco de dados, muito menos especialistas em Postgres, mas sentimos que isso deve ser um problema resolvido, ou uma pessoa que se deparou no passado. Estamos abertos a quaisquer sugestões. Se as informações fornecidas acima não forem suficientes, por favor, comente e adicionarei mais informações conforme necessário.

postgresql transaction
  • 3 3 respostas
  • 16032 Views

3 respostas

  • Voted
  1. Erwin Brandstetter
    2018-07-31T19:34:55+08:002018-07-31T19:34:55+08:00

    A raiz do problema é que, com o READ COMMITTEDnível de isolamento padrão, cada UPSERT simultâneo (ou qualquer consulta) pode ver apenas as linhas que estavam visíveis no início da consulta. O manual:

    Quando uma transação usa esse nível de isolamento, uma SELECTconsulta (sem uma cláusula FOR UPDATE/ SHARE) vê apenas os dados confirmados antes do início da consulta; ele nunca vê dados não confirmados ou alterações confirmadas durante a execução da consulta por transações simultâneas.

    Mas um UNIQUEíndice é absoluto e ainda deve considerar as linhas inseridas simultaneamente - mesmo as linhas invisíveis. Assim, você pode obter uma exceção para uma violação exclusiva, mas ainda não pode ver a linha conflitante na mesma consulta . O manual:

    INSERTcom uma ON CONFLICT DO NOTHINGcláusula pode ter a inserção não prosseguir para uma linha devido ao resultado de outra transação cujos efeitos não são visíveis para o INSERTinstantâneo. Novamente, este é apenas o caso no modo Read Committed.

    A "solução" de força bruta para esse problema é substituir linhas conflitantes por ON CONFLICT ... DO UPDATE. A nova versão de linha fica visível na mesma consulta. Mas existem vários efeitos colaterais e eu aconselho contra isso. Um deles é que os UPDATEgatilhos são disparados - o que você deseja evitar expressamente. Resposta intimamente relacionada no SO:

    • Como usar RETURNING com ON CONFLICT no PostgreSQL?

    A opção restante é iniciar um novo comando (na mesma transação), que pode ver essas linhas conflitantes da consulta anterior. Ambas as respostas existentes sugerem isso. O manual novamente:

    No entanto, SELECTvê os efeitos de atualizações anteriores executadas em sua própria transação, mesmo que ainda não tenham sido confirmadas. Observe também que dois SELECTcomandos sucessivos podem ver dados diferentes, mesmo que estejam em uma única transação, se outras transações confirmarem alterações após o início do primeiro SELECTe antes do início do segundo SELECT.

    Mas você quer mais :

    -- Continuar, usando articleId para representar o artigo para as próximas operações...

    Se as operações de gravação simultâneas puderem alterar ou excluir a linha, para ter certeza absoluta, você também precisará bloquear a linha selecionada . (A linha inserida está bloqueada de qualquer maneira.)

    E como você parece ter transações muito competitivas, para garantir o sucesso, faça um loop até o sucesso. Embrulhado em uma função plpgsql:

    CREATE OR REPLACE FUNCTION f_articleid(_title text, _content text, OUT _articleid int)
      LANGUAGE plpgsql AS
    $func$
    BEGIN
       LOOP
          SELECT articleid
          FROM   articles
          WHERE  title = _title
          FOR    UPDATE          -- or maybe a weaker lock 
          INTO   _articleid;
    
          EXIT WHEN FOUND;
    
          INSERT INTO articles AS a (title, content)
          VALUES (_title, _content)
          ON     CONFLICT (title) DO NOTHING  -- (new?) _content is discarded
          RETURNING a.articleid
          INTO   _articleid;
    
          EXIT WHEN FOUND;
       END LOOP;
    END
    $func$;
    

    Explicação detalhada:

    • O SELECT ou INSERT está em uma função propensa a condições de corrida?
    • 11
  2. Best Answer
    CL.
    2018-07-20T00:59:05+08:002018-07-20T00:59:05+08:00

    Tente o insertprimeiro, com on conflict ... do nothinge returning id. Se o valor já existir, você não obterá nenhum resultado dessa instrução, portanto, será necessário executar a selectpara obter o ID.

    Se duas transações tentarem fazer isso ao mesmo tempo, uma delas será bloqueada insert(porque o banco de dados ainda não sabe se a outra transação será confirmada ou revertida) e continuará somente após a conclusão da outra transação.

    • 10
  3. jjanes
    2018-07-20T07:45:45+08:002018-07-20T07:45:45+08:00

    Eu acho que a melhor solução é apenas fazer a inserção, pegar o erro e tratá-lo corretamente. Se você estiver preparado para lidar com erros, o nível de isolamento serializável é (aparentemente) desnecessário para o seu caso. Se você não estiver preparado para lidar com erros, o nível de isolamento serializável não ajudará - apenas criará ainda mais erros que você não está preparado para lidar.

    Outra opção seria fazer o ON CONFLICT DO NOTHING e então, se nada acontecer, continue fazendo a consulta que você já está fazendo para obter o valor must-be-there-now. Em outras palavras, passe select id into articleId from articles where title = 'my new blog';de uma etapa preventiva para uma etapa executada apenas se ON CONFLICT DO NOTHING de fato não fizer nada. Se for possível que um registro seja inserido e excluído novamente, você deve fazer isso em um loop de repetição.

    • 3

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