Серийный доступ только к ОДНОМУ ресурсу и уровню изоляции...?

Я использую Entity Framework, поэтому обрабатываю долго выполняющиеся задачи (10-30 секунд на avg). У меня много экземпляров рабочих, и каждый работник выбирает следующий идентификатор задачи из таблицы базы данных, и вместе с этим он получает описание работы для этого идентификатора.

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

static int? GetNextDetailId()
{
  int? id = null;
  using ( var ctx = Context.GetContext() )
    using ( var tsx = ctx.Database.BeginTransaction( System.Data.IsolationLevel.Serializable ))
    {
      var obj = ctx.DbsInstrumentDetailRaw.Where( x => x.ProcessState == ProcessState.ToBeProcessed ).FirstOrDefault();
      if ( obj != null )
      {
        id = obj.Id;
        obj.ProcessState = ProcessState.InProcessing;
        ctx.SaveChanges();
      }
      tsx.Commit();
    }

  return id;


} // GetNextDetailId

К сожалению, когда я запускаю его с 10 работниками, я почти сразу получаю

Транзакция (ID процесса 65) была заблокирована для ресурсов блокировки с другим процессом и была выбрана в качестве жертвы тупика. Перезапустите транзакцию.

У меня нет никаких объяснений такого поведения. Я знаю тупиковые ситуации: у нас есть два или более ресурсов и два или более процессов, которые пытаются найти ресурсы не в том же порядке. Но здесь у нас только один ресурс! Все, что я хочу, - это чтобы процессы имели последовательный доступ к этому ресурсу. Таким образом, если A имеет открытую транзакцию, B должен просто подождать, пока A совершит / откатит. Кажется, здесь этого не происходит.

Может кто-нибудь, пожалуйста

  1. пролить свет на то, что здесь происходит, чтобы научить меня.

  2. Дайте ( "THE?") Решение проблемы. Я предполагаю, что эта проблема должна быть очень распространена в программировании.

Спасибо мартин

3 ответа

пролить свет на то, что здесь происходит, чтобы научить меня.

  1. ctx.DbsInstrumentDetailRaw.Where ... получает общую блокировку на столе. С сериализуемым уровнем изоляции эта блокировка удерживается до тех пор, пока транзакция не будет зафиксирована или откатана.
  2. ctx.SaveChanges() Требуется эксклюзивная блокировка для обновления строки.

Две или более транзакций могут одновременно получить общую блокировку на шаге 1, но тогда ни одна из них не может получить эксклюзивную блокировку на шаге 2. Взаимная блокировка.

Дайте ( "THE?") Решение проблемы.

Я могу придумать 2 способа решить эту проблему.

  1. Измените порядок операций: обновите одну строку, а затем верните ее. Вам придется использовать хранимую процедуру, чтобы сделать это в EF.
  2. Используйте более низкий уровень изоляции (например, повторяемое чтение) и оптимистичный параллелизм. Вы не получите взаимоблокировки (общие блокировки будут сняты сразу после select). Когда 2 рабочих пытаются обновить одну и ту же строку, один из них получит исключение параллелизма.

Хорошо, теперь я делаю это:

выберите 1 из InstrumentDetailRaw с помощью (tablockx, holdlock), где 0 = 1"

в начале моей транзакции, которая, согласно этому посту:

Блокировка таблицы с помощью выбора в Entity Framework

делает трюк. Нет тупиков с 10 работниками, работающими часами.

Вы можете проверить это с помощью SQL Profiler, чтобы прослушать операторы SQL, которые выполняются на вашем сервере SQL, но проблема, вероятно, заключается в том, что, даже если вы находитесь внутри транзакции с уровнем изоляции, установленным на сериализуемый, исключительная блокировка не выполняется Таким образом, происходит то, что два потока одновременно обращаются к одной и той же строке, и оба пытаются ее обновить.

Лучший совет, который я видел, заключается в том, что, если вам нужно контролировать блокировку на этом уровне, выполняйте хранимую процедуру или SQL вместо попытки использовать LINQ.

Блокировка таблицы с помощью выбора в Entity Framework

Другие вопросы по тегам