TransactionScope автоматически переходит в MSDTC на некоторых машинах?
В нашем проекте мы используем TransactionScope, чтобы гарантировать, что наш уровень доступа к данным выполняет свои действия в транзакции. Мы стремимся не требовать, чтобы служба MSDTC была включена на компьютерах наших конечных пользователей.
Проблема в том, что на половине машин наших разработчиков мы можем работать с отключенным MSDTC. Другая половина должна включить его, или они получают сообщение об ошибке "MSDTC на [SERVER] недоступен".
Это действительно заставило меня поцарапать голову и всерьез подумать о том, чтобы вернуться к самодовольному TransactionScope-подобному решению на основе объектов транзакций ADO.NET. Это на первый взгляд безумие - тот же код, который работает (и не расширяется) на половине нашего разработчика, на самом деле распространяется на другого разработчика.
Я надеялся на лучший ответ Trace, почему транзакция переходит в DTC, но, к сожалению, это не так.
Вот пример кода, который вызовет проблему: на машинах, которые пытаются эскалировать, он пытается эскалировать во втором соединении.Open() (и да, в данный момент нет другого открытого соединения).
using (TransactionScope transactionScope = new TransactionScope() {
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open();
using (SqlDataReader reader = command.ExecuteReader()) {
// use the reader
connection.Close();
}
}
}
// Do other stuff here that may or may not involve enlisting
// in the ambient transaction
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open(); // Throws "MSDTC on [SERVER] is unavailable" on some...
// gets here on only half of the developer machines.
}
connection.Close();
}
transactionScope.Complete();
}
Мы действительно закопались и попытались выяснить это. Вот некоторая информация о машинах, на которых он работает:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Разработчики, на которых это не работает:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Дев 6: Windows XP X86, SQL2005
- Мой домашний ПК: Windows Vista Home Premium, x86, SQL2005
Я должен добавить, что все машины, чтобы выследить проблему, были полностью исправлены всем, что доступно из Центра обновления Microsoft.
Обновление 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ описывает подобную проблему... еще в 2006 году!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - прочитайте этот пример кода, он ясно демонстрирует подключение с вложенной секундой (ко второму серверу SQL, на самом деле), который перерастет в DTC. Мы не делаем этого в нашем коде - мы не используем разные SQL-серверы, ни разные строки подключения, ни у нас не открываются вложенные вторичные подключения - не должно быть эскалации к DTC.
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (с 2005 года) рассказывает о том, как эскалация к DTC всегда будет происходить при подключении к SQL2000. Мы используем SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN об эскалации транзакций.
На этой странице эскалации транзакций MSDN указано, что следующие условия приведут к эскалации транзакции в DTC:
- По крайней мере один долговременный ресурс, который не поддерживает однофазные уведомления, зачислен в транзакцию.
- По крайней мере два долговременных ресурса, которые поддерживают однофазные уведомления, зачисляются в транзакцию. Например, использование одного соединения с не приводит к продвижению транзакции. Однако всякий раз, когда вы открываете второе соединение с базой данных, в результате чего база данных подключается, инфраструктура System.Transactions обнаруживает, что это второй долговременный ресурс в транзакции, и преобразует его в транзакцию MSDTC.
- Вызывается запрос на "маршализацию" транзакции в другой домен приложения или в другой процесс. Например, сериализация объекта транзакции через границу домена приложения. Объект транзакции является маршалированным по значению, что означает, что любая попытка передать его через границу домена приложения (даже в том же процессе) приводит к сериализации объекта транзакции. Вы можете передать объекты транзакции, вызвав удаленный метод, который принимает транзакцию в качестве параметра, или вы можете попытаться получить доступ к удаленному компоненту, обслуживаемому транзакцией. Это сериализует объект транзакции и приводит к эскалации, как при сериализации транзакции в домене приложения. Он распространяется, и локальный менеджер транзакций больше не подходит.
Мы не переживаем #3. #2 не происходит, потому что есть только одно соединение за раз, и это также к одному "долговременному ресурсу". Есть ли способ, которым #1 могло случиться? Некоторая конфигурация SQL2005/8, которая заставляет его не поддерживать однофазные уведомления?
Обновление 2:
Лично повторно исследовали все версии SQL Server - "Dev 3" на самом деле имеет SQL2008, а "Dev 4" на самом деле SQL2005. Это научит меня никогда больше не доверять моим коллегам.;) Из-за этого изменения данных, я почти уверен, что мы нашли нашу проблему. Наши разработчики SQL2008 не сталкивались с этой проблемой, потому что SQL2008 включал в себя множество замечательных функций, которых нет в SQL2005.
Это также говорит мне, что, поскольку мы собираемся поддерживать SQL2005, мы не можем использовать TransactionScope, как мы это делали, и если мы хотим использовать TransactionScope, нам нужно будет передавать один объект SqlConnection вокруг... что кажется проблематичным в ситуациях, когда нельзя легко обойти SqlConnection... он просто пахнет экземпляром global-SqlConnection. Pew!
Обновление 3
Просто чтобы уточнить здесь в вопросе:
SQL2008:
- Позволяет несколько подключений в одном TransactionScope (как показано в приведенном выше примере кода).
- Предупреждение #1: Если эти несколько SqlConnections вложены, то есть два или более SqlConnections открываются одновременно, TransactionScope немедленно переходит в DTC.
- Предупреждение #2: Если дополнительный SqlConnection открыт для другого "долговременного ресурса" (т. Е. Другого SQL Server), он немедленно переходит в DTC
SQL2005:
- Не разрешает множественные соединения в пределах одного TransactionScope, точка. Он будет увеличиваться, когда / если будет открыто второе SqlConnection.
Обновление 4
Чтобы сделать этот вопрос еще более бесполезным, и просто для большей ясности, вот как вы можете заставить SQL2005 перейти на DTC с помощью одного SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Это просто кажется мне сломанным, но я думаю, что могу понять, если каждый звонок SqlConnection.Open()
захватывает из пула соединений.
"Почему это могло случиться, хотя?" Что ж, если вы используете SqlTableAdapter для этого соединения до его открытия, SqlTableAdapter откроет и закроет соединение, фактически завершив транзакцию за вас, потому что теперь вы не можете повторно открыть его.
Таким образом, в основном, для успешного использования TransactionScope с SQL2005 необходимо иметь какой-то глобальный объект соединения, который остается открытым с момента создания первого TransactionScope до тех пор, пока он больше не понадобится. Помимо запаха кода глобального объекта соединения, открытие первого соединения и его закрытие последним противоречит логике открытия соединения как можно позже и его закрытия как можно скорее.
6 ответов
SQL Server 2008 может использовать несколько SQLConnection
в одном TransactionScope
без эскалации, при условии, что соединения не открываются одновременно, что приведет к множеству "физических" TCP-соединений и, следовательно, потребует эскалации.
Я вижу, что у некоторых из ваших разработчиков есть SQL Server 2005, а у других - SQL Server 2008. Вы уверены, что правильно определили, какие из них обостряются, а какие нет?
Наиболее очевидным объяснением было бы то, что разработчики с SQL Server 2008 - это те, которые не обостряются.
Результат моего исследования по теме:
См. Избежание нежелательной эскалации в распределенных транзакциях.
Я все еще исследую поведение эскалации Oracle: увеличиваются ли транзакции, охватывающие несколько соединений с одной и той же БД, в DTC?
Этот код вызовет эскалацию при подключении к 2005 году.
Проверьте документацию на MSDN - http://msdn.microsoft.com/en-us/library/ms172070.aspx
Рекламные транзакции в SQL Server 2008
В версии 2.0.NET Framework и SQL Server 2005 открытие второго соединения внутри TransactionScope автоматически переводит транзакцию в полную распределенную транзакцию, даже если оба соединения используют одинаковые строки подключения. В этом случае распределенная транзакция добавляет ненужные издержки, которые снижают производительность.
Начиная с SQL Server 2008 и версии 3.5.NET Framework, локальные транзакции больше не преобразуются в распределенные транзакции, если в транзакции открывается другое соединение после закрытия предыдущей транзакции. Это не требует никаких изменений в вашем коде, если вы уже используете пул соединений и участвуете в транзакциях.
Я не могу объяснить, почему Dev 3: Windows 7 x64, SQL2005 успешно и Dev 4: Windows 7 x64 терпит неудачу. Вы уверены, что это не наоборот?
Я не знаю, почему этот ответ был удален, но, похоже, в нем есть какая-то соответствующая информация.
ответил 4 августа 2010 в 17:42 Eduardo
Установите Enlist=false в строке подключения, чтобы избежать автоматического включения в транзакцию.
Вручную подключите соединение в качестве участников в области транзакции. [ оригинальная статья устарела] или сделайте это: Как предотвратить автоматическое продвижение MSDTC [archive.is]
Я не слишком уверен, если вложенное соединение является проблемой. Я вызываю локальный экземпляр сервера SQL, и он не генерирует код неисправности??
public void DoWork2()
{
using (TransactionScope ts2 = new TransactionScope())
{
using (SqlConnection conn1 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;"))
{
SqlCommand cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
cmd.Connection = conn1;
cmd.Connection.Open();
cmd.ExecuteNonQuery();
using (SqlConnection conn2 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;Connection Timeout=100"))
{
cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
cmd.Connection = conn2;
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
ts2.Complete();
}
}
TransactionScope всегда переходит в транзакцию DTC, если вы используете доступ более чем к 1 соединению внутри. Единственный способ, с помощью которого приведенный выше код может работать с отключенным DTC, - это то, что с огромной вероятностью вы оба раза получаете одно и то же соединение из пула соединений.
"Проблема в том, что на половине машин наших разработчиков мы можем работать с отключенным MSDTC". Вы уверены, что он отключен;)
Убедитесь, что ваша connectionString не устанавливает для пула значение false. Это приведет к созданию нового соединения для каждого нового SqlConnection в TransactionScope и эскалации его до DTC.