TransactionScope automatycznie przechodzi na MSDTC na niektórych komputerach?


284

W naszym projekcie używamy TransactionScope's, aby zapewnić, że nasza warstwa dostępu do danych wykonuje swoje działania w transakcji. Naszym celem jest nie wymagać, aby usługa MSDTC była włączona na naszych urządzeniach użytkowników końcowych.

Problem w tym, że na połowie maszyn naszych programistów możemy pracować z wyłączonym MSDTC. Druga połowa musi mieć to włączone lub otrzymają komunikat o błędzie „MSDTC na [SERWER] jest niedostępny” .

Naprawdę zmusza mnie do drapania się po głowie i poważnego zastanawiania się nad wycofaniem się do domowego rozwiązania TransactionScope, opartego na obiektach transakcyjnych ADO.NET. To pozornie szalony - ten sam kod, który działa (i nie eskalować) na pół naszego dewelopera robi eskalacji drugiej dewelopera.

Miałem nadzieję na lepszą odpowiedź dla Trace, dlaczego transakcja została eskalowana do DTC, ale niestety tak nie jest.

Oto przykładowy fragment kodu, który spowoduje problemy, na komputerach, które próbują eskalować, próbuje eskalować przy drugim połączeniu. Open () (i tak, w tym momencie nie jest otwarte żadne inne połączenie).

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();
}

Naprawdę wkopaliśmy się i próbowaliśmy to rozgryźć. Oto kilka informacji na temat maszyn, na których działa:

  • Dev 1: Windows 7 x64 SQL2008
  • Dev 2: Windows 7 x86 SQL2008
  • Dev 3: Windows 7 x64 SQL2005 SQL2008

Programiści, na których nie działa:

  • Dev 4: Windows 7 x64, SQL2008 SQL2005
  • Dev 5: Windows Vista x86, SQL2005
  • Dev 6: Windows XP X86, SQL2005
  • My Home PC: Windows Vista Home Premium, x86, SQL2005

Powinienem dodać, że wszystkie maszyny, starając się znaleźć problem, zostały w pełni załatane wszystkim, co jest dostępne z Microsoft Update.

Aktualizacja 1:

Ta strona eskalacji transakcji MSDN stwierdza, że ​​następujące warunki spowodują eskalację transakcji do DTC:

  1. W transakcji zarejestrowany jest co najmniej jeden trwały zasób, który nie obsługuje powiadomień jednofazowych.
  2. Transakcja obejmuje co najmniej dwa trwałe zasoby, które obsługują powiadomienia jednofazowe. Na przykład rejestracja pojedynczego połączenia nie powoduje promowania transakcji. Jednak za każdym razem, gdy otworzysz drugie połączenie z bazą danych, które powoduje zarejestrowanie bazy danych, infrastruktura System.Transactions wykrywa, że ​​jest to drugi trwały zasób w transakcji, i przekształca go w transakcję MSDTC.
  3. Wywoływane jest żądanie „marshal” transakcji do innej domeny aplikacji lub innego procesu. Na przykład serializacja obiektu transakcji przez granicę domeny aplikacji. Obiekt transakcji jest zestawiany według wartości, co oznacza, że ​​każda próba przekazania go przez granicę domeny aplikacji (nawet w tym samym procesie) powoduje serializację obiektu transakcji. Możesz przekazać obiekty transakcji, wywołując metodę zdalną, która przyjmuje jako parametr Transakcję lub możesz spróbować uzyskać dostęp do zdalnego komponentu obsługiwanego przez transakcję. Powoduje to serializację obiektu transakcji i skutkuje eskalacją, tak jak w przypadku serializacji transakcji w domenie aplikacji. Jest on dystrybuowany, a lokalny menedżer transakcji nie jest już odpowiedni.

Nie doświadczamy # 3. # 2 się nie dzieje, ponieważ istnieje tylko jedno połączenie na raz, a także z jednym „trwałym zasobem”. Czy jest jakikolwiek sposób, żeby mogło się zdarzyć # 1? Jakaś konfiguracja SQL2005 / 8, która powoduje, że nie obsługuje powiadomień jednofazowych?

Aktualizacja 2:

Osobiście ponownie zbadaliśmy wszystkie wersje SQL Server - „Dev 3” faktycznie ma SQL2008, a „Dev 4” to właściwie SQL2005. To nauczy mnie, że nigdy więcej nie zaufam moim współpracownikom. ;) Z powodu tej zmiany danych jestem pewien, że znaleźliśmy nasz problem. Nasi programiści SQL2008 nie mieli problemu, ponieważ SQL2008 zawiera wiele niesamowitych elementów, których nie ma SQL2005.

Mówi mi również, że ponieważ będziemy obsługiwać SQL2005, że nie możemy używać TransactionScope tak jak my, a jeśli chcemy użyć TransactionScope, będziemy musieli przekazać pojedynczy obiekt SqlConnection ... co wydaje się problematyczne w sytuacjach, w których SqlConnection nie można łatwo ominąć ... po prostu pachnie globalną instancją SqlConnection. Ławka w kościele!

Aktualizacja 3

Aby wyjaśnić tutaj w pytaniu:

SQL2008:

  • Umożliwia wiele połączeń w ramach jednego TransactionScope (jak pokazano w powyższym przykładowym kodzie).
  • Zastrzeżenie # 1: Jeśli zagnieżdżonych zostanie wiele SqlConnection, to znaczy, że dwa lub więcej SqlConnection zostanie otwartych w tym samym czasie, TransactionScope natychmiast eskaluje do DTC.
  • Zastrzeżenie # 2: Jeśli dodatkowe SqlConnection zostanie otwarte dla innego „trwałego zasobu” (tj. Innego SQL Servera), natychmiast przejdzie do DTC

SQL2005:

  • Nie zezwala na wiele połączeń w ramach jednego TransactionScope, kropka. Nastąpi eskalacja, gdy / jeśli zostanie otwarte drugie połączenie SqlConnection.

Aktualizacja 4

W interesie czyni to pytanie jeszcze bałagan użyteczne, a tylko przez wzgląd na większą przejrzystość, oto jak można dostać SQL2005 eskalować do DTC z pojedynczym SqlConnection :

using (TransactionScope transactionScope = new TransactionScope()) {
   using (SqlConnection connection = new SqlConnection(connectionString)) {
      connection.Open();
      connection.Close();
      connection.Open(); // escalates to DTC
   }
}

Wydaje mi się to po prostu zepsute, ale chyba rozumiem, czy każde połączenie SqlConnection.Open()jest pobierane z puli połączeń.

„Dlaczego jednak tak się dzieje?” Cóż, jeśli użyjesz SqlTableAdapter przed tym połączeniem przed jego otwarciem, SqlTableAdapter otworzy i zamknie połączenie, skutecznie kończąc transakcję za ciebie, ponieważ teraz nie możesz go ponownie otworzyć.

Zasadniczo więc, aby z powodzeniem korzystać z TransactionScope z SQL2005, potrzebujesz jakiegoś globalnego obiektu połączenia, który pozostaje otwarty od momentu pierwszego TransactionScope jest tworzony, dopóki nie będzie już potrzebny. Oprócz zapachu kodu globalnego obiektu połączenia, otwieranie połączenia jako pierwsze i zamykanie na końcu jest sprzeczne z logiką otwierania połączenia tak późno, jak to możliwe i zamykania go tak szybko, jak to możliwe.


Czy możesz rozwinąć „Rób tutaj inne rzeczy, które mogą, ale nie muszą dotyczyć transakcji otoczenia”. Z pewnością to, co tam jest, ma duży wpływ na zachowanie kodu?
RichardOD

2
„# 2 nie dzieje się, ponieważ istnieje tylko jedno połączenie na raz” - # 2 nie mówi, że drugie połączenie musi być otwarte w tym samym czasie, tylko że musi zostać zarejestrowane w tej samej transakcji.
Joe,

3
Dziękujemy bardzo za zgłoszenie się z aktualizacją 4 pokazującą, w jaki sposób może dojść do eskalacji za pomocą pojedynczego połączenia SqlConnection. Właśnie na to natknąłem się pomimo starannego upewnienia się, że używany jest tylko jeden SqlConnection. Miło wiedzieć, że to komputer jest szalony, a nie ja. :-)
Oran Dennison

Jeśli chodzi o pule połączeń, jeśli mamy wiele połączeń (i zagnieżdżamy się w razie potrzeby), jeśli otwieramy i zamykamy jedno za jednym razem, czy wykorzystujemy 1 prawdziwy zasób puli połączeń lub 1 na połączenie, próbuję to zracjonalizować, aby ustalić czy mieć odpowiednio zakres „przyłączenia” połączenia (którego chciałbym uniknąć)
brumScouse

1
Połączenia zagnieżdżone w tym samym zakresie transakcji będą promować do transakcji rozproszonej. Z SQL Server 2008 i nowszych wiele (nie zagnieżdżających) połączeń w ramach tego samego zakresu transakcji nie będzie promować się do rozproszonego transacitonu.
PreguntonCojoneroCabrón

Odpowiedzi:


71

SQL Server 2008 może używać wielu SQLConnections w jednym TransactionScopebez eskalacji, pod warunkiem, że połączenia nie są otwarte w tym samym czasie, co spowodowałoby wiele „fizycznych” połączeń TCP, a zatem wymagałoby eskalacji.

Widzę, że niektórzy z twoich programistów mają SQL Server 2005, a inni mają SQL Server 2008. Czy jesteś pewien, że poprawnie określiłeś, którzy eskalują, a którzy nie?

Najbardziej oczywistym wytłumaczeniem byłoby to, że programiści z SQL Server 2008 nie rozwijają się.


Tak, szczegóły są prawidłowe i czy ktoś faktycznie patrzy na kod? Istnieją dwa połączenia w zakresie transakcji, jednak zawsze jest tylko jedno połączenie utworzone i otwarte w jednym momencie. Ponadto nie, DTC nie działa na działających maszynach.
Yoopergeek

1
„jednak w danym momencie istnieje tylko jedno połączenie, które zostało nawiązane i otwarte” - dlaczego to ma znaczenie? W przypadku SQL2005, jeśli otworzysz więcej niż jedno połączenie w zakresie transakcji, eskalujesz, czy pozostaną otwarte jednocześnie. Co jest logiczne, jeśli się nad tym zastanowić.
Joe,

Ty i hwiecherzy macie teraz drugie zgadywanie, a ja z niecierpliwością oczekuję na rozpoczęcie pracy w poniedziałek i dokładniejsze sprawdzenie poszczególnych maszyn i upewnienie się, że wersje programu SQL Server są zgodne z wcześniejszymi raportami.
Yoopergeek

19
Ty i hodowcy macie rację. Jajko mam na całej twarzy. Dziękuję za uderzenie mnie kijem wskazującym. :) Ponieważ byłeś pierwszy, dostałeś odpowiedź. Chciałbym jednak dodać jedno wyjaśnienie - SQL2008 pozwala na otwarcie wielu połączeń, ale nie w tym samym czasie. W danym momencie może być otwarte tylko jedno połączenie lub TransactionScope zostanie eskalowany do DTC.
Yoopergeek

@Yoopergeek Mogę zweryfikować, że twoje „nie w tym samym czasie” jest ważne i odpowiednio zredagowałem odpowiedź @Joe. Monitorowanie połączeń TCP podczas testowania wykazało, że stare połączenie TCP zostanie ponownie wykorzystane, gdy połączenia nie będą używane w tym samym czasie, a zatem TransactionScopemoże się przydać z pojedynczym COMMITpo stronie serwera, co uczyniłoby eskalację zbędną.
Eugene Beresovsky,

58

1
Dziękujemy za udostępnienie swoich badań. To naprawdę pomogło. Jeszcze jedno szybkie zapytanie. Jaka jest różnica między TransactionScope () a sqlConnection.BeginTransaction ()?
Baig

Zgodnie z tym żądaniem funkcji ODAC 12C powinien teraz zachowywać się jak SQL 2008, nie promując się do dystrybucji, gdy używa się kolejnych połączeń z tym samym źródłem danych.
Frédéric

31

Że kod będzie powodować eskalację po podłączeniu do 2005 roku.

Sprawdź dokumentację MSDN - http://msdn.microsoft.com/en-us/library/ms172070.aspx

Transakcje, które można promować w programie SQL Server 2008

W wersji 2.0 .NET Framework i SQL Server 2005 otwarcie drugiego połączenia w TransactionScope automatycznie wypromowałoby transakcję do pełnej transakcji rozproszonej, nawet jeśli oba połączenia używałyby identycznych ciągów połączeń. W takim przypadku transakcja rozproszona powoduje niepotrzebne koszty ogólne, które zmniejszają wydajność.

Począwszy od SQL Server 2008 i wersji 3.5 .NET Framework, transakcje lokalne nie są już promowane do transakcji rozproszonych, jeśli inne połączenie zostanie otwarte w transakcji po zamknięciu poprzedniej transakcji. Nie wymaga to żadnych zmian w kodzie, jeśli już korzystasz z puli połączeń i rejestrujesz się w transakcjach.

Nie mogę wyjaśnić, dlaczego Dev 3: Windows 7 x64 odnosi sukcesy, a Dev 4: Windows 7 x64 kończy się niepowodzeniem. Czy na pewno nie jest odwrotnie?


10

Nie wiem, dlaczego ta odpowiedź została usunięta, ale wydaje się, że zawiera pewne istotne informacje.

odpowiedział 4 sierpnia 2010 o 17:42 Eduardo

  1. Ustaw Enlist = false w parametrze połączenia, aby uniknąć automatycznego rejestrowania przy transakcji.

  2. Ręcznie rejestruj połączenie jako uczestników zakresu transakcji. [ oryginalny artykuł jest nieaktualny] lub zrób to: Jak zapobiec automatycznej promocji MSDTC [archive.is]



2

Nie jestem pewien, czy problemem jest połączenie zagnieżdżone. Dzwonię do lokalnej instancji serwera SQL i nie generuje DTC?

    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();
        }
    }

Z której wersji programu SQL Server korzystasz? Zastanawiam się, czy odpowiedź @Peter Meinl wymaga aktualizacji, aby odzwierciedlić jakiekolwiek zmiany wprowadzone w 2008R2 i / lub Denali.
Yoopergeek

Używam SQL Server 2008 R2.
Iftikhar Ali

Zastanawiam się, czy 2008 R2 lepiej się zachowuje? Odpowiedź @hwiechers sprawia, że ​​zastanawiam się, czy wersja Framework, z którą kompilujesz, zapobiega eskalacji. Na koniec zastanawiam się, czy to lokalna instancja R2 robi jakąkolwiek różnicę. Chciałbym mieć czas / zasoby, aby zbadać, jak to się zmieniło wraz z wydaniem 2008 R2 i SQL Server 2012.
Yoopergeek

Nie jesteś pewien, czy problem stanowi połączenie zagnieżdżone? lol ... dobrze kwitnące usuń to !, dlaczego, u licha, ludzie gniazdują, używając oświadczeń, kiedy nie są absolutnie konieczne, nigdy się nie dowiem.
Paul Zahra

1

TransactionScope zawsze przechodzi do transakcji DTC, jeśli korzystasz z dostępu więcej niż 1 połączenie wewnątrz. Jedyny sposób, w jaki powyższy kod może działać z wyłączonym kodem DTC, to jeśli przez ogromne prawdopodobieństwo uzyskasz to samo połączenie z puli połączeń za każdym razem.

„Problem polega na tym, że na połowie maszyn naszych programistów możemy pracować z wyłączonym MSDTC”. Czy na pewno jest wyłączony;)


0

Upewnij się, że parametr connectionString nie ustawia dla puli wartości false. Spowoduje to nowe połączenie dla każdego nowego SqlConnection w TransactionScope i eskaluje je do DTC.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.