Почему уровень изоляции SQL Server возвращается к значению по умолчанию в том же сеансе с использованием EF TransactionScope

У меня проблема с использованием Entity Framework TransactionScopes.

Прочитав документацию и просмотрев несколько примеров и предложений, я реализовал области транзакций для многих запросов, которые есть в моем веб-приложении. Проблема, с которой я столкнулся здесь, связана с уровнями изоляции. Я хочу, чтобы каждый запрос в TransactionScope был ReadUncommited, но по какой-то причине только первый запрос должен иметь желаемый уровень изоляции (READ UNCOMMITED), но все последующие запросы возвращаются к READ COMMITED. Эти запросы читают много данных, и я не против грязного чтения.

Это мой EF TransactionScope и Context (очень простой):

    var transactionOptions = new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted };
    using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled))
    {
         using (var db = new Context())
         {
               //db.Database.ExecuteSqlCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
               //var SessionID = await db.Database.SqlQuery<short>("SELECT @@SPID").FirstOrDefaultAsync();
               //db.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);

               //QUERY1
               var list1= await db.Table1.Include(x => x.ExternalProperty).Where(x => x.Created >= sevenDaysAgo).ToListAsync();                    

               //QUERY2
               var list2 = await db.Table1.Include(x => x.ExternalProperty).Where(x => x.Created >= fourteenDaysAgo && x.Created <= eightDaysAgo).ToListAsync();

                //... Doing more stuff here

                transactionScope.Complete();
         }
     }

QUERY 1 выполняется с READ UNCOMMITED, а QUERY 2 по какой-то причине выполняется с READ COMMITED. Я что-то упускаю? Потому что, как я понимаю, этого не должно происходить, поскольку оба запроса имеют один и тот же TransactionScope.

Я использовал await db.Database.SqlQuery<short>("SELECT @@SPID").FirstOrDefaultAsync(), чтобы получить идентификатор сеанса, зарезервированный контекстом, чтобы убедиться, что используется тот же сеанс.

Я также попытался установить уровень изоляции вручную, используя: db.Database.ExecuteSqlCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");, что привело к тому же поведению.

Я поискал и почти все ответы, предлагающие использовать приведенный выше код. Например: ЭТОТ ОТВЕТ

Почему это могло произойти, особенно если TransactionScope еще не завершен?

Спасибо


person M O H    schedule 25.10.2017    source источник
comment
Не относящееся к делу, рассмотрите возможность использования изоляции моментальных снимков. Вы говорите, что не возражаете против грязного чтения, но READ UNCOMMITTED на самом деле намного хуже - он может пропускать или дублировать строки, если происходит перемещение данных, поэтому ваши результаты не просто потенциально устарели, а совершенно неверны. Не используйте его, если вы не получаете временных результатов, которые все равно будут запрашиваться в ближайшее время (например, в настройках мониторинга).   -  person Jeroen Mostert    schedule 26.10.2017
comment
Нет EF TransactionScope. Это класс .NET, который может охватывать несколько баз данных и поставщиков. Это может перерасти в распределенную транзакцию, если задействовано несколько серверов / провайдеров. Используйте желаемый уровень изоляции с самого начала или запустите вложенную транзакцию с желаемого уровня, если это возможно.   -  person Panagiotis Kanavos    schedule 26.10.2017
comment
И избегайте использования READ UNCOMMITTED или NOLOCK. Это не значит, что не нужно брать замки. Это означает чтение грязных данных. Это не решит никаких проблем параллелизма, вызванных неверными запросами или отсутствием индексов. Вместо этого используйте изоляцию снимков и исправьте все, что вызывает задержки.   -  person Panagiotis Kanavos    schedule 26.10.2017
comment
@JeroenMostert Спасибо за вклад. Эти данные будут запрашиваться каждые несколько минут на регулярной основе. Его цель дать приблизительные оценки по некоторым данным. READ UNCOMMITED - это нормально в этом случае. В противном случае я буду использовать Snapshop Isolation, как вы сказали.   -  person M O H    schedule 26.10.2017
comment
@PanagiotisKanavos Спасибо за информацию. Я понимаю, что это не часть EF, я только хотел объяснить свой сценарий (то есть, как я его использую). Вложенные транзакции могут быть подходящим вариантом. Вы можете привести пример?   -  person M O H    schedule 26.10.2017
comment
@MOH Я бы посоветовал вам не использовать этот уровень изоляции вообще. В 99,9995% случаев это означает, что есть ошибка или неверный запрос. Исправить их   -  person Panagiotis Kanavos    schedule 26.10.2017
comment
@PanagiotisKanavos Запрос занимает несколько секунд. Он просматривает миллионы записей и возвращает данные между диапазонами даты и времени. Я бы сказал, что работает прилично. Если только нам не нужно говорить об индексировании datetime и других настройках производительности, которые не являются предметом этой публикации. Обратите внимание, что запросы в вопросе - пустышки.   -  person M O H    schedule 26.10.2017
comment
Между прочим, включение SELECT в транзакцию, которая выполняется под READ UNCOMMITTED, ничего не делает - нет смысла ни в транзакции, ни в группировке нескольких SELECT таким образом. Предполагая, что у EF нет другого, более элегантного способа указания уровня изоляции (я не знаю, у меня нет опыта работы с EF), вы также можете выполнить оба оператора в их собственных TransactionScopes. Это не повлияет на скорость или правильность, хотя и немного более подробное.   -  person Jeroen Mostert    schedule 26.10.2017
comment
@JeroenMostert Уровень изоляции по умолчанию для базы данных, над которой я работаю, READ COMMITTED с отключенной изоляцией моментальных снимков. У меня есть некоторые блокировки, так как таблица, из которой я читаю, имеет 10-30 новых строк каждую секунду, а также обновления. Поскольку требуемый мне запрос не заботится об изменяемых данных в строке, READ UNCOMMITED предотвратит блокировку, поскольку каждый запрос занимает около 3-7 секунд.   -  person M O H    schedule 26.10.2017
comment
Вы неправильно понимаете - я хотел сказать, что нет необходимости помещать оба оператора в одну и ту же транзакцию (когда в игру вступает сбрасываемый уровень изоляции из-за перезапуска пула соединений), а не то, что изменение уровня изоляции ничего не делает. Другими словами, наличие одного TransactionScope на запрос (каждый из которых выполняется под READ UNCOMMITTED) тривиально решит проблему за счет большего количества кода.   -  person Jeroen Mostert    schedule 26.10.2017
comment
@JeroenMostert Понятно. Это имеет смысл. Спасибо. И теперь я знаю, почему уровень изоляции меняется благодаря тебе. уровень изоляции, сбрасываемый из-за перезапуска пула соединений, вступает в игру.   -  person M O H    schedule 26.10.2017
comment
Кстати, это от (неудачный) дизайн, согласно которому уровень изоляции сбрасывается в результате соединения, проходящего через пул соединений. Microsoft фактически исправила это в SQL Server 2014 только для того, чтобы отменить это, когда они поняли, что вместо этого были нарушены другие сценарии. Я не уверен, почему они не реализовали исправление, заставив SqlConnection снова явно установить уровень изоляции из области при включении, возможно, также проблемы с обратной совместимостью.   -  person Jeroen Mostert    schedule 26.10.2017
comment
Подожди, нет, я путаю. :-) Проблема заключалась в том, что sp_reset_connection не сбрасывает уровень изоляции, что на самом деле является именно тем, что вы хотите в этом сценарии (но действительно вызывает проблемы в другом месте). Не уверен, почему он не работает так, как было задумано для этого случая, когда вы действительно хотите, чтобы он работал таким образом. (Если вы не используете старую версию SQL Server 2014, которая включает исправление.)   -  person Jeroen Mostert    schedule 26.10.2017


Ответы (1)


Если у кого-то нет лучшего ответа, вот что решило мою проблему.

Мне пришлось вручную открывать и закрывать соединение в области транзакции или приказать, чтобы контекст EF не возвращал соединение обратно в пул между каждым запросом.

Я использовал db.Database.Connection.Open(); и db.Database.Connection.Close();

ОБРАТИТЕ ВНИМАНИЕ, что при этом соединение будет оставаться активным до тех пор, пока вы не избавитесь от контекста. Будьте осторожны, так как это может не подходить для вашего сценария.

person M O H    schedule 26.10.2017
comment
Если вы использовали READ UNCOMMITTED из-за блокировки, оставление соединения открытым сделает НАМНОГО хуже для других соединений. READ UNCOMMITED не означает не принимать блокировки, это означает игнорировать чужие блокировки и читать их грязные данные. Блокировки снимаются при закрытии транзакции или соединения. Если транзакция и соединение остаются открытыми, блокировка будет НАМНОГО больше. - person Panagiotis Kanavos; 26.10.2017
comment
@PanagiotisKanavos Я согласен с тем, что оставлять соединение открытым - не лучшая идея. Что я не сказал, что делаю. Я убеждаюсь, что после этого соединение будет закрыто. Обратите внимание, что это единственное место, где у меня есть эта настройка, и его можно вызвать только из одного экземпляра. Это не общедоступный метод, поэтому будет использоваться только один сеанс, а не много. - person M O H; 26.10.2017