Pergunta

Eu tenho o seguinte gráfico de deadlock que descreve duas declarações SQL que estão deadlocking uma à outra. Só não tenho certeza de como analisar esse problema e, em seguida, corrigir meu código SQL para impedir que isso aconteça.

Gráfico principal de deadlock

TEXTO DE ALT HTTP://IMG140.IMAGESHACK.US/IMG140/6193/DEADLOCK1.PNG Clique aqui para uma imagem maior.

Lado esquerdo, detalhes

TEXTO DE ALT HTTP://IMG715.Imageshack.us/img715/3999/deadlock2.png Clique aqui para uma imagem maior.

Lado direito, detalhes

TEXTO DE ALT HTTP://img686.imageshack.us/img686/5097/deadlock3.png Clique aqui para uma imagem maior.

Arquivo XML de Esquema de Deadlock Raw

Clique aqui para baixar o arquivo XML.

Esquema de mesa

TEXTO DE ALT HTTP://IMG509.IMAGESHACH.US/IMG509/5843/DEADLOCKSCHEMA.PNG

Detalhes da tabela de logentries

TEXTO DE ALT HTTP://img28.imageshack.us/img28/9732/deadlockLogentriestable.png

Detalhes da tabela de clientes conectados

TEXTO DE ALT HTTP://img11.imageshack.us/img11/7681/deadlockConnectedClient.png

O que o código está fazendo?

Estou lendo em vários arquivos (por exemplo, digamos 3, para este exemplo) ao mesmo tempo. Cada arquivo contém dados diferentes, mas o mesmo tipo de dados. Eu então insiro os dados em LogEntries tabela e então (se necessário) eu insiro ou excluo algo do ConnectedClients tabela.

Aqui está meu código SQL.

using (TransactionScope transactionScope = new TransactionScope())
{
    _logEntryRepository.InsertOrUpdate(logEntry);

    // Now, if this log entry was a NewConnection or an LostConnection, then we need to make sure we update the ConnectedClients.
    if (logEntry.EventType == EventType.NewConnection)
    {
        _connectedClientRepository.Insert(new ConnectedClient { LogEntryId = logEntry.LogEntryId });
     }

    // A (PB) BanKick does _NOT_ register a lost connection .. so we need to make sure we handle those scenario's as a LostConnection.
    if (logEntry.EventType == EventType.LostConnection ||
        logEntry.EventType == EventType.BanKick)
    {
        _connectedClientRepository.Delete(logEntry.ClientName, logEntry.ClientIpAndPort);
    }

    _unitOfWork.Commit();
    transactionScope.Complete();
}

Agora cada arquivo tem seu próprio UnitOfWork Instância (o que significa que possui seu próprio contexto de conexão, transação e repositório do banco de dados). Portanto, suponho que isso significa que há três conexões diferentes com o banco de dados acontecendo ao mesmo tempo.

Finalmente, isso está usando Entity Framework como o repositório, Mas, por favor, não deixe isso impedi -lo de pensar sobre esse problema.

Usando uma ferramenta de perfil, o Isolation Level é Serializable. Eu também tentei ReadCommited e ReadUncommited, mas os dois erros:-

  • ReadCommited: o mesmo que acima. Impasse.
  • ReadUncommited: erro diferente. Exceção da EF que diz que esperava algum resultado de volta, mas não recebeu nada. Eu acho que este é o LogEntryId Identidade (scope_identity) valor esperado, mas não recuperando por causa da leitura suja.

Por favor ajude!

Ps. É o SQL Server 2008, btw.


Atualização #2

Depois de ler Remus RusanuResposta atualizada, senti que poderia tentar fornecer um pouco mais de informação para ver se alguém pode ajudar ainda mais.

Diagrama EF

TEXTO DE ALT HTTP://img691.imageshack.us/img691/600/deadlockefmodel.png

Agora, Remus sugere (e observe que ele diz que não está familiarizado com a EF) ...

A última peça do quebra -cabeça, o nó esquerdo de trava inexplicável tem no PK_ConnectedClients, presumo que seja da implementação de EF do InserttorUpdate. Provavelmente, ele faz uma pesquisa primeiro e, devido ao relacionamento FK declarado entre ConnectedClients e Logentries, procura no PK_ConnectedClients, adquirindo a trava serializável.

Interessante. Não sei por que o nó esquerdo tem um bloqueio PK_ConnectedClients, como sugerido acima. Ok, vamos conferir o código desse método ....

public void InsertOrUpdate(LogEntry logEntry)
{
    LoggingService.Debug("About to InsertOrUpdate a logEntry");

    logEntry.ThrowIfArgumentIsNull("logEntry");

    if (logEntry.LogEntryId <= 0)
    {
        LoggingService.Debug("Current logEntry instance doesn't have an Id. Instance object will be 'AddObject'.");
        Context.LogEntries.AddObject(logEntry);
    }
    else
    {
        LoggingService.Debug("Current logEntry instance has an Id. Instance object will be 'Attached'.");
        Context.LogEntries.Attach(logEntry);
    }
}

Hum. é simples AddObject (aka. inserir) ou Attach (AKA. Atualização). Sem referências. O código SQL também não sugere nenhum material de pesquisa.

Ok então ... eu tenho outros dois métodos ... talvez eles estejam fazendo algumas pesquisas?

No ConnectedClientRepository ...

public void Insert(ConnectedClient connectedClient)
{
    connectedClient.ThrowIfArgumentIsNull("connectedClient");

    Context.ConnectedClients.AddObject(connectedClient);
}

Não -> também básico, como.

Último método da sorte? Uau .. agora isso é interessante ....

public void Delete(string clientName, string clientIpAndPort)
{
    clientName.ThrowIfArgumentIsNullOrEmpty("clientName");
    clientIpAndPort.ThrowIfArgumentIsNullOrEmpty("clientIpAndPort");

    // First we need to attach this object to the object manager.
    var existingConnectedClient = (from x in GetConnectedClients()
                                   where x.LogEntry.ClientName == clientName.Trim() &&
                                   x.LogEntry.ClientIpAndPort == clientIpAndPort.Trim() &&
                                   x.LogEntry.EventTypeId == (byte)EventType.NewConnection
                                   select x)
                                  .Take(1)
                                  .SingleOrDefault();

    if (existingConnectedClient != null)
    {
        Context.ConnectedClients.DeleteObject(existingConnectedClient);
    }
}

Então, olhando acima, pego uma instância do registro que desejo excluir .. e se existir, exclua -o.

Então .. se eu comentar esse método chamado, na minha lógica inicial até o topo disso, então poste ... o que acontece?

funciona. Wowz.

Também funciona como Serializable ou Read Commited - Ambos funcionam quando eu não chamo o Delete método.

Então, por que esse método excluiria uma trava? É porque o seleção (com serializable) Uma trava e algum impasse acontece?

Com read committed, é possível que eu tenha três chamadas para a exclusão acontecer ao mesmo tempo.

  • 1º pega uma instância dos dados.
  • 2º (e 3º) pega outra instância dos mesmos dados.
  • Agora, o 1º delete. multar.
  • 2º delete .. mas a linha se foi .. então eu entendo esse erro estranho sobre afetou um número inesperado de linhas (0). <== zero itens excluídos.

Possível? Se sim .. er ... como posso consertar isso? Este é um caso clássico de uma condição de corrida? É possível impedir que isso acontecesse de alguma forma?


Atualizações

  • Corrigido os links com as imagens.
  • Link para o arquivo de deadlock XML bruto. Aqui é o mesmo link.
  • Adicionado esquema de tabela de banco de dados.
  • Adicionado os dois detalhes da tabela.
Foi útil?

Solução

O nó do lado esquerdo está segurando um RangeS-U lock sobre PK_CustomerRecords e quer um RangeS-U Trancar i1 (Presumo que seja um índice em LogEntries). O nó do lado direito tem um RangeS-U Trancar i1 e quer um RangeI-N sobre PK_CustomerRecords.

Aparentemente o impasse ocorre entre o _logEntriesRepository.InsertOrUpdate (nó esquerdo) e _connectedClientRepository.Insert (nó direito). Sem saber o tipo de relações de EF declaradas, não posso comentar por que o nó do lado esquerdo tem um bloqueio PK_CustomerRecords No momento, ele insere o LogEntry. Suspeito que isso seja causado por um comportamento do tipo ORM induzido pela EF, como a pesquisa de um membro 'pré -carregado', ou pode ser causado por um transações de nível superior que envolve o escopo do código cortado postado.

Como outros disseram, é necessário publicar o esquema de banco de dados em uma avaliação de impasse, porque o caminho de acesso (índices utilizados) é crítico. Veja meu artigo Leia o impasse Para uma discussão mais detalhada sobre a implicação dos índices em impasse.

Minha primeira recomendação seria forçar o escopo da transação a ser read committed. O nível serializável padrão do TransactionsCopes quase nunca é necessário na prática, é um porco de desempenho e, nesse caso, anúncios de muito ruído desnecessário para a investigação de impasse, trazendo os bloqueios de alcance para a equação, complicando tudo. Por favor, poste as informações de impasse que ocorrem sob leitura comprometida.

Além disso, não poste uma foto do gráfico de impasse. Uma imagem diz que mil palavras não são verdadeiras aqui, publique o deadlock XML original: ela tem muitas informações não visíveis nas fotos bonitas.

Atualizar

Do deadlock xml, posso ver que o nó esquerdo está executando insert [dbo].[LogEntries]([GameFileId], [CreatedOn], [EventTypeId], [Message], [Code], [Violation], [ClientName], [ClientGuid], [ClientIpAndPort]) values (@0, @1, @2, null, null, null, @3, @4, @5) (a <executionStack><frame> elemento). Mais importante, porém, eu posso ver o objeto por trás do índice misterioso 'i1': objectname="AWing.sys.fulltext_index_docidstatus_1755869322" indexname="i1". Então o impasse ocorre em um Índice FullText.

Portanto, a explicação completa do impasse é:

  • O nó direito está em _ConnectedClientRepository.Insert, ele precisa de um bloqueio de inserção de intervalo em PK_ConnectedCliients. Ele possui um bloqueio de intervalos-U no índice fullText i1 da execução anterior de _LogentryRepository.insertorUpdate.
  • O nó esquerdo está no _LogentryRepository.InsertorUpdate, na instrução Insert dentro do lote, e precisa de um bloqueio de intervalos-U no índice de texto completo i1. Possui um bloqueio de intervalos S em PK_ConnectedClients que bloqueia o nó direito, e isso não é explicado por nada no gráfico XML.

A última peça do quebra -cabeça, o nó esquerdo de trava inexplicável tem no PK_ConnectedClients, presumo que seja da implementação de EF do InserttorUpdate. Provavelmente, ele faz uma pesquisa primeiro e, devido ao relacionamento FK declarado entre ConnectedClients e Logentries, procura no PK_ConnectedClients, adquirindo a trava serializável.

O principal culpado aqui é o nível de isolamento da transação (serializável) e o comportamento da EF no insertorupdate. Não posso dar conselhos sobre o comportamento da EF, mas o nível serializável é exagerado, com certeza. O que nos leva de volta ao erro que você recebe no nível de leitura, o que, infelizmente, é novamente um erro de EF em que não posso comentar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top