In quali circostanze viene automaticamente inserita una SqlConnection in una TransactionScope Transaction ambientale?


201

Cosa significa che un "SqlConnection" è "arruolato" in una transazione? Significa semplicemente che i comandi che eseguo sulla connessione parteciperanno alla transazione?

In tal caso, in quali circostanze viene automaticamente inserita una SqlConnection in una TransactionScope Transaction ambientale?

Vedi le domande nei commenti sul codice. La mia ipotesi sulla risposta di ogni domanda segue ogni domanda tra parentesi.

Scenario 1: apertura delle connessioni all'interno di un ambito di transazione

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?)
}

Scenario 2: utilizzo delle connessioni all'interno di un ambito di transazione aperto ESTERNO da esso

//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?)
}

Risposte:


188

Ho fatto alcuni test da quando ho fatto questa domanda e ho trovato la maggior parte se non tutte le risposte da solo, poiché nessun altro ha risposto. Per favore fatemi sapere se ho perso qualcosa.

Q1. Sì, a meno che nella stringa di connessione non sia specificato "enlist = false". Il pool di connessioni trova una connessione utilizzabile. Una connessione utilizzabile è una connessione non inclusa in una transazione o inclusa nella stessa transazione.

Q2. La seconda connessione è una connessione indipendente, che partecipa alla stessa transazione. Non sono sicuro dell'interazione dei comandi su queste due connessioni, poiché sono in esecuzione sullo stesso database, ma penso che possano verificarsi errori se i comandi vengono emessi contemporaneamente su entrambi: errori come "Contesto della transazione in uso da un'altra sessione "

Q3. Sì, viene convertito in una transazione distribuita, quindi l'arruolamento di più di una connessione, anche con la stessa stringa di connessione, fa sì che diventi una transazione distribuita, che può essere confermata controllando un GUID non nullo su Transaction.Current.TransactionInformation .DistributedIdentifier. * Aggiornamento: ho letto da qualche parte che ciò è stato risolto in SQL Server 2008, in modo che MSDTC non venga utilizzato quando si utilizza la stessa stringa di connessione per entrambe le connessioni (purché entrambe le connessioni non siano aperte contemporaneamente). Ciò consente di aprire una connessione e chiuderla più volte all'interno di una transazione, il che potrebbe fare un uso migliore del pool di connessioni aprendo le connessioni il più tardi possibile e chiudendole il prima possibile.

Q4. No. Una connessione aperta quando non era attivo alcun ambito di transazione, non verrà automaticamente inclusa in un ambito di transazione appena creato.

Q5. No. A meno che non si apra una connessione nell'ambito della transazione o non si arruoli una connessione esistente nell'ambito, non vi è praticamente alcuna transazione. La connessione deve essere arruolata automaticamente o manualmente nell'ambito della transazione per consentire ai comandi di partecipare alla transazione.

Q6. Sì, i comandi su una connessione che non partecipano a una transazione vengono sottoposti a commit come emessi, anche se sembra che il codice sia stato eseguito in un blocco dell'ambito della transazione che è stato eseguito il rollback. Se la connessione non è inclusa nella portata della transazione corrente, non partecipa alla transazione, quindi il commit o il rollback della transazione non avrà alcun effetto sui comandi emessi su una connessione non inclusa nella portata della transazione ... come ha scoperto questo ragazzo . È molto difficile da individuare se non si capisce il processo di arruolamento automatico: si verifica solo quando viene aperta una connessione all'interno di un ambito di transazione attivo.

Q7. Sì. Una connessione esistente può essere esplicitamente arruolata nell'ambito della transazione corrente chiamando EnlistTransaction (Transaction.Current). Puoi anche arruolare una connessione su un thread separato nella transazione utilizzando una DependentTransaction, ma come in precedenza, non sono sicuro di come due connessioni coinvolte nella stessa transazione contro lo stesso database possano interagire ... e potrebbero verificarsi errori, e ovviamente la seconda connessione arruolata provoca l'escalation della transazione a una transazione distribuita.

Q8. Potrebbe essere stato generato un errore. Se è stato utilizzato TransactionScopeOption.Required e la connessione era già inclusa in una transazione dell'ambito della transazione, non si verifica alcun errore; infatti, non è stata creata alcuna nuova transazione per l'ambito e il conteggio delle transazioni (@@ trancount) non aumenta. Se, tuttavia, si utilizza TransactionScopeOption.RequiresNew, viene visualizzato un messaggio di errore utile quando si tenta di arruolare la connessione nella nuova transazione dell'ambito della transazione: "La connessione è attualmente nella lista. Termina la transazione corrente e riprova." E sì, se completi la transazione in cui è inclusa la connessione, puoi tranquillamente arruolare la connessione in una nuova transazione. Aggiornamento: se in precedenza hai chiamato BeginTransaction sulla connessione, viene generato un errore leggermente diverso quando si tenta di arruolarsi in una nuova transazione dell'ambito della transazione: "Impossibile eseguire l'iscrizione nella transazione perché è in corso una transazione locale sulla connessione. Termina la transazione locale e riprovare." D'altra parte, puoi tranquillamente chiamare BeginTransaction su SqlConnection mentre è arruolato in una transazione dell'ambito della transazione e ciò aumenterà effettivamente @@ trancount di uno, a differenza dell'uso dell'opzione Necessario di un ambito di transazione nidificato, che non lo causa aumentare. È interessante notare che se poi si procede alla creazione di un altro ambito di transazione nidificata con l'opzione Richiesto, non si otterrà un errore,

Q9. Sì. I comandi partecipano a qualsiasi transazione in cui è inclusa la connessione, indipendentemente dall'ambito della transazione attiva nel codice C #.


11
Dopo aver scritto la risposta a Q8, mi rendo conto che queste cose stanno iniziando a sembrare complicate come le regole di Magic: The Gathering! Tranne questo è peggio, perché la documentazione di TransactionScope non spiega nulla di tutto ciò.
Triynko,

Per Q3, stai aprendo due connessioni contemporaneamente usando la stessa stringa di connessione? Se è così, allora quella sarà una transazione distribuita (anche con SQL Server 2008)
Randy supporta Monica

2
No. Sto modificando il post per chiarire. La mia comprensione è che avere due connessioni aperte contemporaneamente provocherà sempre una transazione distribuita, indipendentemente dalla versione di SQL Server. Prima di SQL 2008, l'apertura di una sola connessione alla volta, con la stessa stringa di connessione, avrebbe comunque causato un DT, ma con SQL 2008, l'apertura di una connessione alla volta (non averne due aperte contemporaneamente) con la stessa stringa di connessione non causava un DT
Triynko,

1
Per chiarire la tua risposta per Q2, i due comandi dovrebbero essere eseguiti correttamente se eseguiti in sequenza sullo stesso thread.
Jared Moore,

2
Sulla questione della promozione Q3 per stringhe di connessione identiche in SQL 2008, ecco la citazione MSDN: msdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx
pseudocoder

19

Bel lavoro Triynko, le tue risposte mi sembrano tutte accurate e complete. Alcune altre cose che vorrei sottolineare:

(1) Arruolamento manuale

Nel tuo codice sopra, mostri (correttamente) l'arruolamento manuale in questo modo:

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

Tuttavia, è anche possibile farlo in questo modo, usando Enlist = false nella stringa di connessione.

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

C'è un'altra cosa da notare qui. Quando viene aperto conn2, il codice del pool di connessioni non sa che si desidera successivamente arruolarlo nella stessa transazione di conn1, il che significa che a conn2 viene assegnata una connessione interna diversa da conn1. Quindi, quando si connette conn2, ora ci sono 2 connessioni arruolate, quindi la transazione deve essere promossa a MSDTC. Questa promozione può essere evitata solo usando l'arruolamento automatico.

(2) Prima di .Net 4.0, consiglio vivamente di impostare "Transaction Binding = Explicit Unbind" nella stringa di connessione . Questo problema è stato risolto in .Net 4.0, rendendo Expb Unbind totalmente inutile.

(3) Rotolare il proprio CommittableTransactione impostarlo Transaction.Currentsu è essenzialmente la stessa cosa di ciò che TransactionScopefa. Questo è raramente effettivamente utile, solo FYI.

(4) Transaction.Current è thread-static. Ciò significa che Transaction.Currentè impostato solo sul thread che ha creato il file TransactionScope. Pertanto non è possibile eseguire più thread eseguendo lo stesso TransactionScope(possibilmente utilizzando Task).


Ho appena testato questo scenario e sembra funzionare come descritto. Inoltre, anche se si utilizza l'arruolamento automatico, se si chiama "SqlConnection.ClearAllPools ()" prima di aprire la seconda connessione, viene convertito in una transazione distribuita.
Triynko,

Se questo è vero, allora può esserci solo una singola connessione "reale" coinvolta in una transazione. La possibilità di aprire, chiudere e riaprire una connessione inclusa in una transazione TransactionScope senza passare a una transazione distribuita è quindi davvero un'illusione creata dal pool di connessioni , che normalmente lascia aperta la connessione disposta e restituisce la stessa connessione esatta se re - aperto per arruolamento automatico.
Triynko,

Quindi quello che stai davvero dicendo è che se elimini il processo di arruolamento automatico, quando vai a riaprire una nuova connessione all'interno di una transazione dell'ambito di transazione (TST), invece del pool di connessioni afferrando la connessione corretta (quella originariamente arruolato nel TST), prende in modo abbastanza appropriato una connessione completamente nuova, che una volta arruolata manualmente fa sì che il TST si intensifichi.
Triynko,

Ad ogni modo, è esattamente quello che stavo suggerendo nella mia risposta a Q1 quando ho detto che è arruolato a meno che nella stringa di connessione non sia specificato "Enlist = false", quindi ho parlato di come il pool trova una connessione adatta.
Triynko,

Per quanto riguarda il multi-threading, se visiti il ​​link nella mia risposta a Q2, vedrai che mentre Transaction.Current è univoco per ogni thread, puoi facilmente acquisire il riferimento in un thread e passarlo a un altro thread; tuttavia, l'accesso a un TST da due thread diversi provoca un errore molto specifico "Contesto di transazione in uso da un'altra sessione". Per eseguire il multi-thread di un TST, è necessario creare una transazione dipendente, ma a quel punto deve essere una transazione distribuita, poiché è necessaria una seconda connessione indipendente per eseguire effettivamente i comandi simultanei e MSDTC per coordinare i due.
Triynko,

1

Un'altra situazione bizzarra che abbiamo visto è che se ne costruisci uno EntityConnectionStringBuildersi confonderà TransactionScope.Currente (pensiamo) arriverà nella transazione. Abbiamo osservato questo nel debugger, dove TransactionScope.Current's current.TransactionInformation.internalTransactionspettacoli enlistmentCount == 1prima di costruire, e enlistmentCount == 2dopo.

Per evitarlo, costruiscilo dentro

using (new TransactionScope(TransactionScopeOption.Suppress))

e forse al di fuori dell'ambito della tua operazione (la stavamo costruendo ogni volta che avevamo bisogno di una connessione).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.