Почему эти два SQL-оператора тупиковые? (График тупика + подробности включены) - PullRequest
4 голосов
/ 20 марта 2010

У меня есть следующий график взаимоблокировок, который описывает два SQL-оператора, которые взаимоблокируют друг друга. Я просто не уверен, как проанализировать эту проблему, а затем исправить свой код SQL, чтобы этого не происходило.

График основного тупика

альтернативный текст http://img140.imageshack.us/img140/6193/deadlock1.png Нажмите, чтобы увидеть полный размер изображения

Левая сторона, детали

альтернативный текст http://img715.imageshack.us/img715/3999/deadlock2.png Нажмите, чтобы увидеть полный размер изображения.

Правая сторона, подробности

альтернативный текст http://img686.imageshack.us/img686/5097/deadlock3.png Нажмите, чтобы увидеть полный размер изображения

XML-файл необработанной тупиковой схемы

Нажмите здесь, чтобы загрузить XML-файл .

Схема таблицы

альтернативный текст http://img509.imageshack.us/img509/5843/deadlockschema.png

LogEntries Таблица подробностей

альтернативный текст http://img28.imageshack.us/img28/9732/deadlocklogentriestable.png

Подключенные клиенты Таблица деталей

альтернативный текст http://img11.imageshack.us/img11/7681/deadlockconnectedclient.png

Что делает код?

Я читаю в нескольких файлах (например, скажем, 3, для этого примера) одновременно . Каждый файл содержит разные данные, НО один и тот же тип данных. Затем я вставляю данные в таблицу LogEntries, а затем (при необходимости) вставляю или удаляю что-то из таблицы ConnectedClients.

Вот мой 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();
}

Теперь у каждого файла есть собственный экземпляр UnitOfWork (что означает, что у него есть своя собственная база данных, транзакция и контекст репозитория). Так что я предполагаю, что это означает, что есть 3 разных соединения с БД, которые происходят одновременно.

Наконец, это использует Entity Framework в качестве хранилища, , но, пожалуйста, не позволяйте этому помешать вам подумать об этой проблеме .

Используя инструмент профилирования, Isolation Level равен Serializable. Я также пытался ReadCommited и ReadUncommited, но они оба ошибки: -

  • ReadCommited: то же, что и выше. Тупик.
  • ReadUncommited: другая ошибка. Исключение EF, которое говорит, что оно ожидало некоторого результата назад, но ничего не получило. Я предполагаю, что это значение LogEntryId Identity (scope_identity), которое ожидается, но не извлекается из-за грязного чтения.

Пожалуйста, помогите!

PS. Это Sql Server 2008, кстати.


Обновление № 2

Прочитав Remus Rusanu * обновленный ответ , я почувствовал, что могу попытаться предоставить немного больше информации, чтобы узнать, может ли кто-то еще помочь.

Диаграмма EF

альтернативный текст http://img691.imageshack.us/img691/600/deadlockefmodel.png

Теперь, Ремус предлагает (и заметьте, он говорит, что не знаком с EF) ...

Последний кусок головоломки, необъяснимый замок левый узел имеет на PK_ConnectedClients, я предполагаю, является от реализации EF InsertOrUpdate. Это, вероятно, делает поиск в первую очередь, и из-за FK отношения объявлены между ConnectedClients и LogEntries, это ищет на PK_ConnectedClients, следовательно получение сериализуемого замка.

Интересно. Я не уверен, почему левый узел имеет блокировку на PK_ConnectedClients, как предложено выше. Хорошо, давайте проверим код для этого метода ....

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);
    }
}

Хм. это просто AddObject (он же вставка) или Attach (он же обновление). Нет ссылок. Sql-код также не намекает на поиск.

Хорошо, тогда ... у меня есть два других метода ... может быть, они делают некоторые поиски?

В ConnectedClientRepository ...

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

    Context.ConnectedClients.AddObject(connectedClient);
}

Нет -> также простой, как.

Удачный последний метод? Ух ты .. теперь это интересно ....

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);
    }
}

Итак, глядя выше, я беру экземпляр записи, которую хочу удалить ... и если она существует, то удаляю ее.

Итак ... если я закомментирую этот вызов метода, в моей первоначальной логической манере до самого верха этого SO сообщения ... что происходит?

это работает. WOWZ.

Он также работает как Serializable или Read Commited - оба работают, когда я не вызываю метод Delete.

Так почему же этот метод удаления получает блокировку? Это происходит потому, что select (с serializable) блокирует и возникает некоторая тупиковая ситуация?

При read committed возможно ли, чтобы у меня было 3 вызова, чтобы удаление произошло одновременно.

  • 1-й захватывает данные.
  • 2-й (и 3-й) получает другой экземпляр тех же данных.
  • Теперь 1-е удаление. хорошо.
  • 2-й удаляет .. но строка ушла .. поэтому я получаю странную ошибку о , затронувшую неожиданное количество строк (0). <== удалено ноль элементов. </li>

возможно? Если так .. эээ ... как я могу это исправить? Это классический случай состояния гонки? Можно ли как-то предотвратить это от счастья?


Обновление

  • Исправлены ссылки на изображения.
  • Ссылка на необработанный файл тупика XML. Здесь по той же ссылке .
  • Добавлена ​​схема таблицы базы данных.
  • Добавлены обе детали таблицы.

1 Ответ

4 голосов
/ 21 марта 2010

Левый боковой узел удерживает RangeS-U lock на PK_CustomerRecords и хочет RangeS-U блокировку на i1 (я предполагаю, что его индекс на LogEntries). Правый боковой узел имеет RangeS-U блокировку на i1 и хочет RangeI-N на PK_CustomerRecords.

По-видимому, тупик возникает между _logEntriesRepository.InsertOrUpdate (левый узел) и _connectedClientRepository.Insert (правый узел). Не зная типа объявленных отношений EF, я не могу комментировать, почему левый боковой узел имеет блокировку на PK_CustomerRecords в тот момент, когда он вставляет LogEntry. Я подозреваю, что это вызвано либо поведением типа ORM, вызванным EF, например поиском «предварительно загруженного» члена, либо оно может быть вызвано высокоуровневым TransactionScope, который окружает область действия в отсканированном коде.

Как уже говорили другие, необходимо опубликовать схему базы данных в оценке взаимоблокировки, поскольку путь доступа (используемые индексы) является критическим. См. Мою статью тупик чтения-записи для более подробного обсуждения значения индексов в взаимоблокировках.

Моя первая рекомендация - установить область действия транзакции на read committed. Сериализуемый по умолчанию уровень TransactionScopes практически никогда не требуется на практике, он снижает производительность, и в этом конкретном случае добавляет много ненужного шума в расследование тупиковой ситуации, вводя в уравнение блокировки диапазона, усложняя все. Пожалуйста, опубликуйте информацию о взаимоблокировке, которая возникает при прочтении.

Кроме того, не размещайте изображение графика тупиковой ситуации. Картинка говорит, что тысяча слов не соответствует действительности, выложите оригинальный тупиковый XML: в нем много информации, невидимой на красивых картинках.

Обновление

Из тупикового XML я вижу, что левый узел выполняет insert [dbo].[LogEntries]([GameFileId], [CreatedOn], [EventTypeId], [Message], [Code], [Violation], [ClientName], [ClientGuid], [ClientIpAndPort]) values (@0, @1, @2, null, null, null, @3, @4, @5) (элемент <executionStack><frame>). Но что еще более важно, я вижу объект за загадочным индексом 'i1': objectname="AWing.sys.fulltext_index_docidstatus_1755869322" indexname="i1". Таким образом, тупик возникает по полному текстовому индексу .

Итак, полное объяснение тупика:

  • правый узел находится в _connectedClientRepository.Insert, ему требуется блокировка вставки диапазона для PK_ConnectedClients. Он имеет блокировку RangeS-U для полнотекстового индекса i1 из ранее выполненного _logEntryRepository.InsertOrUpdate.
  • левый узел находится в _logEntryRepository.InsertOrUpdate, в операторе INSERT внутри пакета, и ему требуется блокировка RangeS-U для полнотекстового индекса i1. Он имеет блокировку RangeS-S для PK_ConnectedClients, которая блокирует правый узел, и это не объясняется ничем в графе XML.

Последний фрагмент головоломки, необъяснимый левый узел блокировки на PK_ConnectedClients, я предполагаю, это из EF-реализации InsertOrUpdate. Вероятно, сначала выполняется поиск, и из-за связи FK, объявленной между ConnectedClients и LogEntries, он ищет PK_ConnectedClients, следовательно, получая сериализуемую блокировку.

Основным виновником здесь являются уровень изоляции транзакции (Serializable) и поведение EF в InsertOrUpdate. Я не могу дать совет по поведению EF, но сериализуемый уровень наверняка излишний. Это возвращает нас к ошибке, полученной при уровне чтения, что, к сожалению, снова является ошибкой EF, которую я не могу комментировать.

...