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