Почему эти два оператора sql блокируются?(График тупиковой ситуации + подробности включены)

StackOverflow https://stackoverflow.com/questions/2483294

Вопрос

У меня есть следующий график взаимоблокировок, который описывает два оператора 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 Личность (scope_identity) значение, которое ожидается, но не может быть получено из-за грязного чтения.

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

ПС.Кстати, это Sql Server 2008.


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

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

Диаграмма EF

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

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

Последний кусок головоломки, необъяснимый левый узел блокировки на pk_connectedclients, я предполагаю, что из реализации EF insertorUpdate.В первую очередь он делает поиск, и из -за отношений FK, объявленных между подключенными клиентами и логическими, он ищет на pk_connectectclients, следовательно, получение сериализуемого блокировки.

Интересный.Я не уверен, почему левый узел заблокирован. 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...что происходит?

оно работает.ВАУЗ.

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

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

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

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

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


Обновления

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

Решение

Левый боковой узел содержит 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, которую я не могу комментировать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top