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 / 58425
Accepted
deadLock
deadLock
Asked: 2014-02-07 23:40:24 +0800 CST2014-02-07 23:40:24 +0800 CST 2014-02-07 23:40:24 +0800 CST

Como evitar uma condição de corrida com chamadas de proc cross db? e outras preocupações

  • 772

Nem tenho certeza se essa pergunta é necessária, mas estou curioso para saber a opinião de todos. Tenho dois bancos de dados no mesmo servidor, dbFoo, dbBar. dbFoo tem a tabela a seguir, observe que este é um exemplo simplificado e a sintaxe pode não estar correta, pois estou com pressa e muito mais interessado na resposta para o problema subjacente do que no código para fazê-lo ...

CREATE TABLE dbo.CodeNumbers(
CodeNumbersID INT IDENTITY (1,1) NOT NULL PRIMARY KEY,
CodeValue VARCHAR(30) NOT NULL
IsUsed BIT NOT NULL DEFAULT(0)
);

dbo.CodeNumbersé preenchido com um CSV mensal fornecido, o método de importação de sua escolha já está escrito para colocá-los lá. NUNCA recebemos um código duplicado.

Vamos supor, por causa dos argumentos, que temos 10.000.000 linhas na tabela. que todos seguem este formato quando importados:

1, 'ajdirjfisofklrlfo039402', 0 all the way till
10000000, 'fkeiir9489', 0

Agora em dbBar eu tenho 2 procedimentos armazenados, o primeiro deve acessar o primeiro código não usado em dbFoo, retorná-lo em uma variável de saída e marcá-lo como usado. Então eu tenho algo como:

CREATE PROCEDURE GetNextUseableCode
   @CodeOut VARCHAR(30) OUTPUT,
   @CID INT OUTPUT
AS
   SELECT @CID = CodeNumbersID, @CodeOut = CodeValue
   FROM dbFoo.dbo.CodeNumbers
   WHERE IsUsed = 0

   UPDATE dbFoo.dbo.CodeNumbers
   SET IsUsed = 1
   WHERE CodeNumbersID = @CID 

O código que chama o procedimento de dbBar é acessado por 200k sessões por dia em vários horários. Quando dbFoo.Codesnão tem mais para retornar, está tudo bem, está tudo bem, o aplicativo é simplesmente informado, desculpe, não volte mais amanhã.

Tenho 3 perguntas principais..

  1. Existe algo especial que eu precisaria ter no código para evitar condições de corrida e, em caso afirmativo, o que seria melhor para lidar com isso sem deixar o sistema de joelhos.

  2. É uma maneira eficiente de garantir que o próximo código obtido sempre que o procedimento for chamado, seja o próximo em ordem cronológica na coluna de ID.

  3. Existe alguma outra preocupação que não estou analisando agora que possa gerar grandes problemas e qual seria uma maneira eloqüente de lidar com essa situação?

Entendo que esta é uma pergunta longa e bastante aberta, tenho algumas soluções codificadas, mas sinto que há maneiras muito melhores de obter os resultados que desejo.

Desde já agradeço como sempre por toda a ajuda.

sql-server sql-server-2012
  • 1 1 respostas
  • 2560 Views

1 respostas

  • Voted
  1. Best Answer
    Aaron Bertrand
    2014-02-08T06:42:35+08:002014-02-08T06:42:35+08:00

    Não há nada em você SELECTque dite a ordem. Também não está protegido de duas sessões lendo a mesma linha. Para ver que não é seguro:

    1. Crie uma tabela descartável com uma coluna chave.

      CREATE TABLE dbo.GeneratedIDs(ID INT PRIMARY KEY);
      
    2. Execute este código em um loop de duas sessões diferentes:

      SET NOCOUNT ON;
      
      DECLARE @DECLARE @CID INT, @CodeOUt VARCHAR(30);
      
      SELECT @CID = CodeNumbersID, @CodeOut = CodeValue
        FROM dbFoo.dbo.CodeNumbers
        WHERE IsUsed = 0
      
      UPDATE dbFoo.dbo.CodeNumbers
        SET IsUsed = 1
        WHERE CodeNumbersID = @CID 
      
      INSERT dbo.GeneratedIDs SELECT @CID;
      
      GO 100000
      
    3. Verifique a saída para estes - eles acontecerão:

      Msg 2627, Nível 14, Estado 1
      Violação da restrição PRIMARY KEY 'PK_hexcode'. Não é possível inserir chave duplicada no objeto 'dbo.GeneratedIDs'. O valor da chave duplicada é (<algum valor>).

      Ou você pode ver impasses se agrupar o SELECT/ UPDATEem uma transação explícita:

      Msg 1205, nível 13, estado 52 A
      transação (ID do processo 57) foi bloqueada em recursos de bloqueio com outro processo e foi escolhida como a vítima do bloqueio. Execute novamente a transação.

    Para contornar esse problema e garantir que o ID obtido seja o menor disponível, faça o seguinte:

    BEGIN TRANSACTION;
    
    SELECT TOP (1) @CID = ...
    FROM dbFoo.dbo.CodeNumbers WITH (XLOCK, HOLDLOCK)
    WHERE IsUsed = 0
    ORDER BY CodeNumbersID;
    
    UPDATE ...
    
    COMMIT TRANSACTION;
    

    Observe que adicionei uma transação explícita e também XLOCK/ HOLDLOCKhints para impedir que duas sessões simultâneas leiam a mesma linha. Claro, isso tem um impacto na simultaneidade (que, infelizmente, é exatamente o que você quer e precisa aqui).

    Outras maneiras de fazer isso incluem apenas atualizar a linha e usar uma variável de tabela para capturar valores da OUTPUTcláusula:

    DECLARE @x TABLE(CodeOut VARCHAR(30), CID INT);
    
    ;WITH x AS 
    (
      SELECT TOP (1) CodeNumbersID, CodeValue, IsUsed
        FROM dbFoo.dbo.CodeNumbers WITH (XLOCK, HOLDLOCK)
        WHERE IsUsed = 0
        ORDER BY CodeNumbersID
    )
    UPDATE x SET IsUsed = 1
      OUTPUT inserted.CodeValue, inserted.CodeNumbersID INTO @x;
    
    SELECT @CodeOut = CodeOut, @CID = CID FROM @x;
    

    Pela atualização de Paul, sim, você também pode fazer isso sem a variável de tabela:

    ;WITH x AS 
    (
      SELECT TOP (1) CodeNumbersID, CodeValue, IsUsed
        FROM dbFoo.dbo.CodeNumbers WITH (XLOCK, HOLDLOCK)
        WHERE IsUsed = 0
        ORDER BY CodeNumbersID
    )
    UPDATE x 
      SET @CodeOut = x.CodeValue, @CID = x.CodeNumbersID, IsUsed = 1;
    

    (Embora eu não goste muito dessa sintaxe; não sei por quê. Pode ser a mesma razão pela qual sempre esqueço que ela existe.)

    Você pode alterar o chamador para esperar um conjunto de resultados em vez de dois parâmetros de saída, mas isso também é um trabalho extra. Em ambos os casos, você ainda precisa garantir que obteve o ID mais baixo disponível, o que provavelmente significa um CTE com SELECTas mesmas dicas. Alguma discussão aqui . Também falo sobre uma abordagem semelhante nesta postagem do blog, mas não entrei em nada sobre simultaneidade e duas sessões tentando excluir a mesma linha ao mesmo tempo. Obviamente, nesse caso, apenas um deles pode vencer, mas com um UPDATEambos podem conseguir ter sucesso (pelo menos em teoria).

    Para facilitar as coisas, você pode relaxar a restrição de que o "próximo" ID distribuído é o menor ID disponível. Mas você ainda precisa do isolamento por meio das dicas para garantir que duas sessões simultâneas não leiam o mesmo valor (o que pode acontecer independentemente da ordem). Esperançosamente, com um índice adequado, eles não destruirão a simultaneidade.

    • 9

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

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