@footballer

Обясните по коннекшен пулу и транзакциям в EF и SQL Server — почему так написано по ссылкам?

Тут https://coderwall.com/p/jnniww/why-you-shouldn-t-u... пишут :
Here we go, since DbContext use the same connection string , a connection we get is the same connection returned from connection pool, therefore we run into the ambient transaction and see partial data which has not been fixed in database yet.

Здесь https://stackoverflow.com/questions/21202982/one-t... пишут:
No, this was a coincidence because the 2nd context reused the connection of the 1st from the connection pool. This is not guaranteed and will break under load.

Как я понял, в обоих источниках челы имеют ввиду, что соединения с sql сервером хранятся в пуле соединений. И там написано, что если я в Entity Framework создаю new DBContext , потом диспозю его и снова создаю новый new DBContext, то из-за того, что у оба DBContext будут иметь одну и ту же строку подключения (у меня в приложении только одна ДБ, поэтому и строка подключения в конфиге одна-единственная), то получится так, что после закрытия первого DBContext реальное подключение с sql сервером не разорвется, а останется висеть в пуле подключений (а почему?)?
По первой ссылке вообще написано, что из-за того, что 2 разных DBContext (из разных потоков) будут иметь общую строку подключения, то из пула подключений им придет один и тот же коннекшен, поэтому независимо от того, в каких уровнях изоляций будут выполняться запросы в обоих DBContext , sql server будет считать, что запросы от обоих DBContext являются одной транзакцией (хотя оба DBContext вызываются из разных потоков независимо). Но это ж бред, потому что тогда бы не было никакого смысла использовать транзакции в многопользовательских системах, потому что строка подключения к БД лежит в конфиге и всегда общая для всех DBContext , а для разных пользователей системы в разных потоках запускаются разные DBContext , которые должны выполнять запросы в своих собственных транзакциях, но из-за того, что строка подключения общая, то получится, что никаких собственных транзакций у DBContext не будет, а будет одна общая транзакция на все параллельно выполняющиеся DBContext .
По первой ссылке дальше еще пишут
you're right, this doesn't work if you use one DbContext instance for multiple threads.

I never run into the problem that the connection pool was an issue. Every DbContext instance is associated with a single connection (standard settings). No matter what you do, the DbContext will reset the connection on dispose so that it can be used again by other consumers.

то есть, тут чел имеет ввиду, что если DbContext создан заново, то он не должен шарить общий коннекшен с sql сервером. Но предыдущие челы же явно пишут, что если строка подключения общая, то и соединение из пула возьмется общее, т.е. даже если создать заново DbContext, он должен юзать старое соединение, что будет приводить к проблемам с транзакциями.

В общем, кто-нибудь объяснить это по-нормальному. Что имели ввиду те челы по ссылкам (может быть, я что-то там неправильно понял). И вообще, есть ли опасность, что два разных new DbContext , каждый из которых будет в разных транзакциях и при этом оба имеющие одну строку подключения, будут выполняться в одном подключении к sql серверу и вызывать баги с транзакциями?
  • Вопрос задан
  • 1297 просмотров
Пригласить эксперта
Ответы на вопрос 2
AndyKorg
@AndyKorg
Кнопконажиматель и припоерасплавлятель
Проверить очень просто - запустить SQL Server Profiler и посмотреть SPID под которым приходят запросы на сервер. Если SPID разные то и сессии разные.
Ответ написан
Комментировать
@footballer Автор вопроса
Если SPID разные то и сессии разные.

Сессии разные - значит, коннекшены тоже разные, сессия одна - коннекшен один, так же?
Мне передали прогу на EntityFramework, внутри нее подряд несколько раз создается new DbContext. Каждый DbContext обернут в свою собственую TransactionScope. Т.к. все DbContext представляют одну логич. операцию, то сейчас реализовано коряво, и мне нужно убрать все индивидуальные транзакции у каждого DbContext и обернуть их всех в 1 транзакцию. Чтобы много не переделывать, я пока решил не заменять создание нескольких DbContext на 1, а просто обернул все DbContext внутрь одного TransactionScope. У первого DbContext я удалил его собственную транзакцию, у второго забыл удалить (получилось, что у меня в проге стало 2 транзакции - внешняя и вложенная). Получился сокращенно такой код:
using (var transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions {IsolationLevel = IsolationLevel.Snapshot})) 
{
   var data1Id;
   using (var dbContext = new DB()) 
   {
	   var data1= new Data1();
	   dbContext.Table1.Add(data1);
	   /*1*/dbContext.SaveChanges();
	   data1Id = data1.Id;
   }
   if (data1Id != 0)
   {
		using (var transactionScope2 = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions {IsolationLevel = IsolationLevel.Snapshot})) 
		{
			using (var dbContext = new DB()) 
			{
				dbContext.Table2.Add(new Data2() { Data1Id = data1Id, ... });
				 /*2*/dbContext.SaveChanges();
				transactionScope2.Complete();
			}
		}
   }
   transactionScope.Complete();
}

Запустил прогу, первый DbContext выполнил запросы внутри внешней транзакции (произошел инсерт, но пока без коммита), второй DbContext начал выполнять запросы внутри своей вложенной транзакции. В итоге второй DbContext простоял в дебаге полминуты, а потом кинулся эксепшен что-то типа "ожидание времени окончания операции истекло", все транзакции роллбэкнулись. Я посмотрел в SQL Server Profiler - запросы из разных DbContext имели разный SPID. В итоге я сделал себе такой вывод - т.к. SPID разные, то и коннекшены были разные, значит, sql сервер воспринимал запросы от разных DbContext как параллельные (каждый в своих собственных транзакциях), хотя по логике проги это должны были быть взаимозависимые последовательные запросы внутри одной транзакции. В итоге произошла взаимная блокировка (кстати, я не понял, почему, т.к. разные DbContext инсертили в разные таблицы), и транзакция откатилась (или какая там ошибка могла произойти?).
Получается, что транзакция откатилась, потому что 2 разных DbContext имели разные транзакции и разные сессии (коннекшены). Если бы был 1 общий DbContext (соотвественно, 1 общий коннекшен), то транзакция бы выполнилась успешно, как я понимаю.
Я начал гуглить и нашел те ссылки. Если я их правильно понял, в них говорится, что не факт, что 2 разных DbContext будут иметь разные коннекшены, потому что "они шарят общую строку подключения, и поэтому коннекшен для второго DbContext может взяться тот же самый, что и для первого DbContext, потому что коннекшен первого DbContext сохранится в пуле коннекшенов даже после закрытия первого DbContext". Вот это меня и напрягло и заставило тут задать вопрос. Потому что это же получается, что если у меня многопользовательская система и разные контроллеры обрабатывают независимые запросы разных юзеров в разных потоках, и каждый контроллер, естественно, создает свой new DbContext , то даже если я оберну весь код в транзакции, то все-равно строка подключения к БД одинаковая у всех DbContext, значит, они могут юзать один-единственный коннекшен из пула коннекшенов, это значит, что PSID будет один и тот же, и sql сервер будет выполнять их в общей транзакции, а не в индивидуальных. Но такого же не может быть, иначе бы просто не было смысла открывать транзакции.
Кто-нибудь может это объяснить?
Еще то, что я не понял:
1) в коде выше после прохождения точки /*1*/dbContext.SaveChanges(); профайлер пишет "BEGIN TRANSACTION" и PSID = 53 (все вроде бы правильно, транзакция началась во время первого инсерта), а в точке /*2*/dbContext.SaveChanges(); профайлер пишет "BEGIN TRANSACTION" и PSID = 59 (но тут я не понял, транзакция второго DbContext же вложенная, но в новой сессии с PSID = 59 открывается только один "BEGIN TRANSACTION". Это значит, что первая (внешняя) транзакция теряется для всех последующих открытых сессий после того, как первая сессия "захватила" во время выполнения инсерта эту транзакцию, несмотря на то, что все последующие сессии в коде открываются внутри этой внешней транзакции? Или как? Это значит, что вторая сессия выполнялась только внутри одной транзакции, хотя в коде она вложена в 2 транзакции?).
2) уровень изоляции задан как snaphot, но это никак не отображается в профайлере. Профайлер показывает только строку "BEGIN TRANSACTION". Я хочу убедиться, что на sql сервер правильно передается уровень изоляции, но не могу, т.к. не вижу этого в профайлере. Возможно, нужно включить больше чекбоксов во вкладке "выбор событий", но я не могу найти по названию событий, какой чекбокс нужно для этого включить. В общем, подскажите, что сделать, чтобы профайлер показывал уровень изоляции.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы