W jakich okolicznościach SqlConnection automatycznie włącza się do transakcji ambient TransactionScope?

Co to znaczy, że SqlConnection jest "włączony" do transakcji? Czy to po prostu oznacza, że polecenia, które wykonam na połączeniu, będą uczestniczyć w transakcji?

Jeśli tak, to w jakich okolicznościach SqlConnection Automatycznie włącza się do transakcji ambient TransactionScope?

Zobacz pytania w komentarzach do kodu. Zgaduję, że odpowiedź na każde pytanie następuje po każdym pytaniu w nawiasie.

Scenariusz 1: Otwarcie połączenia wewnątrz zakresu transakcji

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{   
    // Q1: Is connection automatically enlisted in transaction? (Yes?)
    //
    // Q2: If I open (and run commands on) a second connection now,
    // with an identical connection string,
    // what, if any, is the relationship of this second connection to the first?
    //
    // Q3: Will this second connection's automatic enlistment
    // in the current transaction scope cause the transaction to be
    // escalated to a distributed transaction? (Yes?)
}

Scenariusz 2: wykorzystanie połączeń wewnątrz zakresu transakcji, które zostały otwarte poza nim

//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
    // Connection was opened before transaction scope was created
    // Q4: If I start executing commands on the connection now,
    // will it automatically become enlisted in the current transaction scope? (No?)
    //
    // Q5: If not enlisted, will commands I execute on the connection now
    // participate in the ambient transaction? (No?)
    //
    // Q6: If commands on this connection are
    // not participating in the current transaction, will they be committed
    // even if rollback the current transaction scope? (Yes?)
    //
    // If my thoughts are correct, all of the above is disturbing,
    // because it would look like I'm executing commands
    // in a transaction scope, when in fact I'm not at all, 
    // until I do the following...
    //
    // Now enlisting existing connection in current transaction
    conn.EnlistTransaction( Transaction.Current );
    //
    // Q7: Does the above method explicitly enlist the pre-existing connection
    // in the current ambient transaction, so that commands I
    // execute on the connection now participate in the
    // ambient transaction? (Yes?)
    //
    // Q8: If the existing connection was already enlisted in a transaction
    // when I called the above method, what would happen?  Might an error be thrown? (Probably?)
    //
    // Q9: If the existing connection was already enlisted in a transaction
    // and I did NOT call the above method to enlist it, would any commands
    // I execute on it participate in it's existing transaction rather than
    // the current transaction scope. (Yes?)
}
Author: Triynko, 2010-05-21

3 answers

Zrobiłem kilka testów, odkąd zadałem to pytanie i znalazłem większość, jeśli nie wszystkie odpowiedzi na własną rękę, ponieważ nikt inny nie odpowiedział. Proszę dać mi znać, jeśli coś przeoczyłem.

Q1. tak, chyba że w łańcuchu połączeń podano "enlist=false". Pula połączeń znajduje użyteczne połączenie. Użyteczne połączenie to takie, które nie jest włączone do transakcji lub takie, które jest włączone do tej samej transakcji.

Q2. drugie połączenie jest niezależnym połączenie, które uczestniczy w tej samej transakcji. Nie jestem pewien interakcji poleceń na tych dwóch połączeniach, ponieważ działają one przeciwko tej samej bazie danych, ale myślę, że mogą wystąpić błędy, jeśli polecenia są wydawane na obu w tym samym czasie: błędy takie jak "kontekst transakcji w użyciu przez inną sesję"

Q3. tak, następuje eskalacja transakcji rozproszonej, więc połączenie więcej niż jednego połączenia, nawet z tym samym łańcuchem połączeń, powoduje staje się rozproszoną transakcją, która może być potwierdzona przez sprawdzenie, czy nie ma null GUID w transakcji.Aktualne.Informacje dotyczące transakcji.DistributedIdentifier. * Aktualizacja: czytałem gdzieś, że jest to naprawione w SQL Server 2008, tak że MSDTC nie jest używany, gdy ten sam ciąg połączenia jest używany dla obu połączeń (o ile oba połączenia nie są otwarte w tym samym czasie). To pozwala otworzyć połączenie i zamknąć je wiele razy w ramach transakcji, co może lepiej wykorzystać Pula połączeń poprzez otwieranie połączeń tak późno, jak to możliwe i zamykanie ich tak szybko, jak to możliwe.

Q4.Nie. Połączenie otwarte, gdy żaden zakres transakcji nie był aktywny, nie zostanie automatycznie włączone do nowo utworzonego zakresu transakcji.

Q5.Nie. Jeśli nie otworzysz połączenia w zakresie transakcji lub nie zaciągniesz istniejącego połączenia w zakresie, zasadniczo nie ma żadnej transakcji. Twoje połączenie musi być automatycznie lub ręcznie włączone do zakres transakcji, aby Twoje polecenia mogły uczestniczyć w transakcji.

Q6. tak, polecenia na połączeniu nie uczestniczącym w transakcji są zatwierdzane zgodnie z wydanymi, nawet jeśli kod został wykonany w bloku zakresu transakcji, który został wycofany. Jeśli połączenie nie jest włączone do bieżącego zakresu transakcji, nie uczestniczy w transakcji, więc zatwierdzenie lub cofnięcie transakcji nie będzie miało wpływu na polecenia wydane na połączenie nieuwzględnione w zakresie transakcji... jak się dowiedział ten facet. Jest to bardzo trudne do wykrycia, jeśli nie rozumiesz automatycznego procesu rekrutacji: występuje tylko wtedy, gdy połączenie jest otwarte wewnątrz aktywnego zakresu transakcji.

Q7.Tak. Istniejące połączenie może być jawnie włączone do bieżącego zakresu transakcji, wywołując EnlistTransaction (transakcja.Prąd). Można również nawiązać połączenie na osobnym wątku w Transakcja za pomocą DependentTransaction, ale jak wcześniej, nie jestem pewien, jak dwa połączenia zaangażowane w tej samej transakcji z tej samej bazy danych mogą współdziałać... i mogą wystąpić błędy, a oczywiście drugie połączenie powoduje eskalację transakcji do transakcji rozproszonej.

Q8. błąd może zostać wyrzucony. If TransactionScopeOption.Wymagane zostało użyte, a połączenie zostało już zapisane w transakcji zakresu transakcji, wtedy nie ma błędu; w rzeczywistości nie ma nowej transakcji utworzonej dla zakresu, a liczba transakcji (@@trancount) nie wzrasta. Jeśli jednak korzystasz z TransactionScopeOption.Requriesnew, następnie otrzymasz pomocny komunikat o błędzie podczas próby pozyskania połączenia w nowej transakcji zakres transakcji: "połączenie aktualnie zawiera transakcję. Zakończ bieżącą transakcję i Ponów próbę."I tak, jeśli sfinalizujesz transakcję, w której jest zapisane połączenie, możesz bezpiecznie zarejestrować połączenie w nowa transakcja. Aktualizacja: jeśli wcześniej wywołałeś BeginTransaction przy połączeniu, podczas próby zarejestrowania się w nowym obszarze transakcji pojawia się nieco inny błąd: "nie można zarejestrować się w transakcji, ponieważ trwa lokalna transakcja przy połączeniu. Zakończ lokalną transakcję i Ponów próbę."Z drugiej strony, można bezpiecznie zadzwonić BeginTransaction na SqlConnection, podczas gdy jest on zapisany w transakcji zakresu transakcji, a to faktycznie zwiększy @@trancount o jeden, w przeciwieństwie do używania wymaganej opcji zagnieżdżonego zakresu transakcji, co nie powoduje jego zwiększenia. Co ciekawe, jeśli następnie utworzysz kolejny zagnieżdżony zakres transakcji z wymaganą opcją, nie pojawi się błąd, ponieważ nic się nie zmienia w wyniku posiadania już aktywnej transakcji zakresu transakcji (pamiętaj @ @ trancount nie jest zwiększany, gdy transakcja zakresu transakcji jest już aktywna, a wymagana opcja jest aktywna). używany).

Q9.Tak. Polecenia biorą udział w każdej transakcji, w której jest zawarte połączenie, niezależnie od tego, jaki jest aktywny zakres transakcji w kodzie C#.

 189
Author: Triynko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:14

Dobra robota Triynko, Twoje odpowiedzi wyglądają mi na dość dokładne i kompletne. Kilka innych rzeczy chciałbym zwrócić uwagę:

(1) Instrukcja rekrutacji

W powyższym kodzie (poprawnie) pokazujesz ręczny zapis tak:

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}

Jest jednak również możliwe, aby zrobić to w ten sposób, używając Enlist=false w łańcuchu połączeń.

string connStr = "...; Enlist = false";
using (TransactionScope ts = new TransactionScope())
{
    using (SqlConnection conn1 = new SqlConnection(connStr))
    {
        conn1.Open();
        conn1.EnlistTransaction(Transaction.Current);
    }

    using (SqlConnection conn2 = new SqlConnection(connStr))
    {
        conn2.Open();
        conn2.EnlistTransaction(Transaction.Current);
    }
}

Jest jeszcze jedna rzecz do odnotowania tutaj. Gdy conn2 jest otwarty, kod puli połączeń nie wie, że chcesz później włączyć go do tej samej transakcji co conn1, co oznacza, że conn2 otrzymuje inne wewnętrzne połączenie niż conn1. Następnie, gdy conn2 jest zarejestrowany, są teraz 2 połączenia, więc transakcja musi być promowana do MSDTC. Tej promocji można uniknąć tylko za pomocą automatycznego naboru.

(2) przed. Net 4.0 bardzo polecam ustawienie "Transaction Binding=Explicit Unbind" w łańcuchu połączeń. Ten problem został rozwiązany w. Net 4.0, dokonywanie wyraźnego rozłączania jest całkowicie niepotrzebne.

(3) Toczenie własnego CommittableTransaction i ustawienie Transaction.Current na to jest zasadniczo tym samym, co robi TransactionScope. To rzadko się przydaje, tylko dla twojej informacji.

(4) Transaction.Current jest nitką statyczną. Oznacza to, że {[3] } jest ustawione tylko na wątku, który utworzył TransactionScope. Tak więc wiele wątków wykonujących to samo TransactionScope (ewentualnie używając Task) nie jest możliwe.

 19
Author: Jared Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:59

Inną dziwną sytuacją, jaką widzieliśmy, jest to, że jeśli zbudujesz EntityConnectionStringBuilder, to będzie się sypać z TransactionScope.Current i (myślimy) zaciągnąć się do transakcji. Zaobserwowaliśmy to w debuggerze, gdzie TransactionScope.Current ' s current.TransactionInformation.internalTransaction pokazuje enlistmentCount == 1 przed konstruowaniem, a enlistmentCount == 2 później.

Aby tego uniknąć, zbuduj go wewnątrz

using (new TransactionScope(TransactionScopeOption.Suppress))

I być może poza zakresem Twojej operacji(konstruowaliśmy ją za każdym razem, gdy potrzebowaliśmy połączenia).

 1
Author: Todd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 22:36:14