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 SQL2005 SQL2008

Разработчики, на которых это не работает:

  • Dev 4: Windows 7 x64, SQL2008 SQL2005
  • Dev 5: Windows Vista x86, SQL2005
  • Дев 6: Windows XP X86, SQL2005
  • Мой домашний ПК: Windows Vista Home Premium, x86, SQL2005

Я должен добавить, что все машины, чтобы выследить проблему, были полностью исправлены всем, что доступно из Центра обновления Microsoft.

Обновление 1:

На этой странице эскалации транзакций MSDN указано, что следующие условия приведут к эскалации транзакции в DTC:

  1. По крайней мере один долговременный ресурс, который не поддерживает однофазные уведомления, зачислен в транзакцию.
  2. По крайней мере два долговременных ресурса, которые поддерживают однофазные уведомления, зачисляются в транзакцию. Например, использование одного соединения с не приводит к продвижению транзакции. Однако всякий раз, когда вы открываете второе соединение с базой данных, в результате чего база данных подключается, инфраструктура System.Transactions обнаруживает, что это второй долговременный ресурс в транзакции, и преобразует его в транзакцию MSDTC.
  3. Вызывается запрос на "маршализацию" транзакции в другой домен приложения или в другой процесс. Например, сериализация объекта транзакции через границу домена приложения. Объект транзакции является маршалированным по значению, что означает, что любая попытка передать его через границу домена приложения (даже в том же процессе) приводит к сериализации объекта транзакции. Вы можете передать объекты транзакции, вызвав удаленный метод, который принимает транзакцию в качестве параметра, или вы можете попытаться получить доступ к удаленному компоненту, обслуживаемому транзакцией. Это сериализует объект транзакции и приводит к эскалации, как при сериализации транзакции в домене приложения. Он распространяется, и локальный менеджер транзакций больше не подходит.

Мы не переживаем #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

  1. Установите Enlist=false в строке подключения, чтобы избежать автоматического включения в транзакцию.

  2. Вручную подключите соединение в качестве участников в области транзакции. [ оригинальная статья устарела] или сделайте это: Как предотвратить автоматическое продвижение 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.

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