2017-02-13 11 views
0

Я хотел бы использовать оптимистичный параллелизм с TransactionScope. Вот код, который я придумал до сих пор:TransactionScope и оптимистический параллелизм

var options = new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted}; 
using (var scope = new TransactionScope(TransactionScopeOption.Required, options)) 
{ 
    using (var connection = new SqlConnection(_connectionString)) 
    { 
     // ... execute some sql code here 

     // bump up version 
     var version = connection.ExecuteScalar<DateTime>(@" 
      DECLARE @version datetime2 = SYSUTCDATETIME(); 
      UPDATE [Something].[Test] 
       SET [Version] = @version 
       WHERE Id = @Id 
      SELECT @version 
     ", new {Id = id}); 

     // ... execute more sql code here 

     // check if version has not changed since bump up 
     // NOTE: version is global for the whole application, not per row basis 
     var newVersion = connection.ExecuteScalar<DateTime>("SELECT MAX([Version]) FROM [Something].[Test]"); 
     if (newVersion == version) scope.Complete(); // looks fine, mark as completed 
    } 
} // what about changes between scope.Complete() and this line? 

К сожалению, этот код вызывает серьезную проблему. Между проверкой версии и фиксацией транзакции могут быть некоторые изменения в базе данных. Это стандартная ошибка time of check to time of use. Единственный способ, с помощью которого я могу это решить, - выполнить проверку версии и совершить транзакцию как одну команду.

Возможно ли выполнить некоторый код SQL вместе с фиксацией транзакции с использованием TransactionScope? Если нет, то какое другое решение можно использовать?

EDIT1: Версия должна быть за приложение, а не за строку.

EDIT2: Я мог бы использовать сериализуемый уровень изоляции, но это не вариант из-за проблем с производительностью, которые могут возникнуть.

+0

Кажется, что самым простым решением было бы переместить весь этот код sql в хранимую процедуру и поместить его в транзакцию. Честное продвижение sql из вашего приложения - хорошая идея в любом случае создать многоуровневую архитектуру и отделить код от реализации данных. –

+0

@SeanLange Это изменение должно быть выполнено на уже существующей системе, и перемещение всей логики в хранимые процедуры не является вариантом. –

ответ

3

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

Это просто неправда. Конструктор по умолчанию TransactionSope, как и в коде, который вы опубликовали, использует уровень изоляции Serializable. Хотя это, возможно, problem, он имеет побочный эффект preventing any modification to any row you queried. Это пессимистический контроль параллелизма.

Вы правы, что вам следует использовать оптимистичный контроль параллелизма. Вам необходимо использовать a TransactionScope constructor that accepts TransactionOptions и передать возможность использовать более приличный isolation level, например. читать прочитано. Что касается версии строки, используйте простой int, который вы увеличиваете с каждой записью в приложении.

UPDATE [Something].[Test] 
SET ..., [Version] = @new_version 
OUTPUT Inserted.Id 
WHERE Id = @Id AND [Version] = @old_version; 

@old_version версия, которую Вы нашли на записи при запросе его. @new_version - @old_version+1. Если строка была изменена после ее чтения, WHERE не найдет ее, и ваш результат будет пустым, поэтому вы знаете, что вам нужно читать, обновлять и повторять попытку (возник конфликт). Это a well known optimistic control scheme.

Обратите внимание, что этот оптимистичный контроль параллелизма имеет больше смысла, когда чтение и запись занимают две разные транзакции (например, читать в T1, отображать пользовательскую форму, а затем записывать в T2). Когда чтение и запись происходят в той же транзакции, тогда лучше оставить его на движке. Я бы просто использовал snapshot isolation level, который решает проблему из коробки.

+0

Если честно, в реальном коде я изменил уровень изоляции на ReadCommitted. Однако не включил его в этот образец. Будет редактировать в данный момент. –

+0

Что касается вашего решения. Похоже, я не очень хорошо описывал свою проблему. Мне нужно иметь глобальный счетчик, а не на каждую строку. Я не могу использовать serializable, потому что будет много чтений и блокировка нескольких таблиц на каждой записи не является вариантом. Изменит описание вопроса в одно мгновение. –

+0

«Мне нужно иметь глобальный счетчик, а не на каждую строку»: сохранение такого счетчика при параллелизме - это, в основном, * невозможно *. Что не так с счетчиком в строке, почему вы не можете его использовать? –

 Смежные вопросы

  • Нет связанных вопросов^_^