2010-05-18 33 views
12

Estoy buscando una descripción de la raíz de este error: "contexto de transacción en uso por otra sesión".Cuál es el motivo de "contexto de transacción en uso por otra sesión"

Lo obtengo a veces en uno de mis unittests así que no puedo proporcionar el código de repro. Pero me pregunto qué es el motivo "por diseño" del error.

ACTUALIZACIÓN: el error vuelve como SqlException de SQL Server 2008. Un lugar donde aparece el error parece ser de subproceso único. Pero probablemente tengo interacciones unittest, ya que recibo el error donde ejecuto varias pruebas a la vez (MSTest en VS2008sp1). Pero la prueba no se parece a:

  • crear un objeto y guardar dentro de DB-transacción (commit)
  • crear TransactionScope
  • intentar abrir una conexión - aquí llego SqlException con tales StackTrace:

.

System.Data.SqlClient.SqlException: Transaction context in use by another session. 
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) 
    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) 
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest) 
    at System.Data.SqlClient.SqlInternalConnectionTds.PropagateTransactionCookie(Byte[] cookie) 
    at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx) 
    at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx) 
    at System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction) 
    at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction) 
    at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject) 
    at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) 
    at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) 
    at System.Data.SqlClient.SqlConnection.Open() 

he encontrado estos mensajes:

pero no puedo entender lo que "hilos múltiples compartiendo la misma transacción en un ámbito de transacción causará la siguiente excepción: 'Contexto de transacción en uso por otra sesión.' " significa. Todas las palabras son comprensibles, pero no el punto.

De hecho, puedo compartir una transacción del sistema entre subprocesos. Y hay incluso un mecanismo especial para esto: clase DependentTransaction y método Transaction.DependentClone.

Estoy intentando reproducir un caso de uso a partir del primer mensaje:

  1. hilo principal crea transacción DTC, recibe DependentTransaction (creada usando Transaction.Current.DependentClone en el hilo principal
  2. hilo hijo 1 enlista en esta transacción DTC al crear un ámbito de transacción basado en la transacción dependiente (aprobada mediante constructor)
  3. El hilo secundario 1 abre una conexión
  4. El hilo secundario 2 se alista en la transacción DTC al crear un alcance de transacción bas ed de la transacción dependiente (pasado a través de constructor)
  5. hilo Niño 2 abre una conexión

con dicho código:

using System; 
using System.Threading; 
using System.Transactions; 
using System.Data; 
using System.Data.SqlClient; 

public class Program 
{ 
    private static string ConnectionString = "Initial Catalog=DB;Data Source=.;User ID=user;PWD=pwd;"; 

    public static void Main() 
    { 
     int MAX = 100; 
     for(int i =0; i< MAX;i++) 
     { 
      using(var ctx = new TransactionScope()) 
      { 
       var tx = Transaction.Current; 
       // make the transaction distributed 
       using (SqlConnection con1 = new SqlConnection(ConnectionString)) 
       using (SqlConnection con2 = new SqlConnection(ConnectionString)) 
       { 
        con1.Open(); 
        con2.Open(); 
       } 
       showSysTranStatus(); 

       DependentTransaction dtx = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 
       Thread t1 = new Thread(o => workCallback(dtx)); 
       Thread t2 = new Thread(o => workCallback(dtx)); 
       t1.Start(); 
       t2.Start(); 
       t1.Join(); 
       t2.Join(); 

       ctx.Complete(); 
      } 
      trace("root transaction completes"); 
     } 
    } 
    private static void workCallback(DependentTransaction dtx) 
    { 
     using(var txScope1 = new TransactionScope(dtx)) 
     { 
      using (SqlConnection con2 = new SqlConnection(ConnectionString)) 
      { 
       con2.Open(); 
       trace("connection opened"); 
       showDbTranStatus(con2); 
      } 
      txScope1.Complete(); 
     } 
     trace("dependant tran completes"); 
    } 
    private static void trace(string msg) 
    { 
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + msg); 
    } 
    private static void showSysTranStatus() 
    { 
     string msg; 
     if (Transaction.Current != null) 
      msg = Transaction.Current.TransactionInformation.DistributedIdentifier.ToString(); 
     else 
      msg = "no sys tran"; 
     trace(msg); 
    } 

    private static void showDbTranStatus(SqlConnection con) 
    { 
     var cmd = con.CreateCommand(); 
     cmd.CommandText = "SELECT 1"; 
     var c = cmd.ExecuteScalar(); 
     trace("@@TRANCOUNT = " + c); 
    } 
} 

Se produce un error en la llamada de TransactionScope completa de raíz. Pero el error es diferente: Excepción no controlada: System.Transactions.TransactionInDoubtException: la transacción está en duda. ---> pired. El período de tiempo de espera transcurrido antes de la finalización de la operación o el servidor no responde.

En resumen: quiero entender qué significa "Contexto de transacción en uso por otra sesión" y cómo reproducirlo.

+0

Un pequeño punto: ¿estás seguro de que tienes una transacción distribuida? Hace 2 conexiones abiertas: con1.Open(); con2.Open(); Pero la cadena de connestion es la misma y está usando Sql2008. AFAIK si usa Sql2008 Y la misma cadena de conexión, la transacción no escalará a distribuida. Sigue siendo "local". Mis 2 centavos. –

+0

No, la apertura de dos conexiones simultáneas siempre conduce a una transacción distribuida. No importa que tengan las mismas cadenas de conexión o no – Shrike

+0

mi error. Lo siento. –

Respuesta

2

"Multiple threads sharing the same transaction in a transaction scope will cause the following exception: 'Transaction context in use by another session.'"

Suena bastante sencillo. Si alistas dos conexiones diferentes en la misma transacción y tratas de emitir comandos en cada una de las dos conexiones, simultáneamente, a partir de diferentes subprocesos, podría producirse un conflicto.

En otras palabras, un hilo está emitiendo un comando en una conexión y tiene algún tipo de bloqueo en el contexto de la transacción. El otro subproceso, utilizando la otra conexión, intenta ejecutar comandos al mismo tiempo, y no puede bloquear el mismo contexto de transacción, que está siendo utilizado por el otro subproceso.

+0

La fase es directa, pero no el punto. Mostré un ejemplo con el uso multiproceso de System.Transactions. Y no hubo tal error (pero hay otro). Además, eche un vistazo a esta publicación de un chico del equipo SysTrans (creo): http://www.pluralsight-training.net/community/blogs/jimjohn/archive/2005/05/01/7923.aspx – Shrike

+0

Y si SysTrans no es compatible con el entorno multiproceso, ¿por qué necesitamos el tipo DependantTransaction? – Shrike

1

Da un paso atrás y concéntrate más en tu código y menos en la información de múltiples hilos que flotan.

Si su escenario no implica enhebrar, podría estar relacionado con piezas que no están cerradas como esperaba.

Quizás el código sql al que llama no llegue a esa instrucción de transacción confirmada. O hay algo más involucrado en ese nivel. Tal vez utilizó una instancia de SqlConnection configurando la transacción en el código .net y está reutilizando esa misma instancia en el otro código que usa TransactionScope. Intente agregar instrucciones de uso() cuando corresponda, para asegurarse de que todo esté cerrado como espera.

0

La forma en que trataría este problema al construir sentencias Linq con objetos mutlipe es tener un constructor para cada clase que tome un contexto de datos y un método GetDataContext() correspondiente en cada clase. cuando se combinan las clases, me gustaría nuevo hasta las instancias de clases que pasan en GetContext de la primera clase()

public class CriterionRepository : ICriterionRepository 
    { 

     private Survey.Core.Repository.SqlDataContext _context = new Survey.Core.Repository.SqlDataContext(); 

     public CriterionRepository() { } 

     public CriterionRepository(Survey.Core.Repository.SqlDataContext context) 
     {    
      _context = context; 
     } 

... 


     public Survey.Core.Repository.SqlDataContext GetDataContext() 
     { 
      return _context; 
     } 

} 
0

debe crear una DependentTransaction para cada hilo una continuación en el interior del hilo de crear & abrir la conexión db dentro de un TransacctionScope usando el dependentTransaction en el ctor.

  //client code/main thread 
      using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, timeout)) 
      { 
       Transaction currentTransaction = Transaction.Current; 
       currentTransaction.TransactionCompleted += OnCompleted; 
       DependentTransaction dependentTransaction; 
       int nWorkers = Config.Instance.NumDBComponentWorkers; 
       for (int i = 0; i < nWorkers; i++) 
       { 
        dependentTransaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 
        this.startWorker(dependentTransaction); 
       } 
       do 
       { 
        //loop + wait 
        Thread.Sleep(150); 
       } while (this.status == DBComponentStatus.Running); 
       //No errors-commit transaction 
       if (this.status == DBComponentStatus.Finished && this.onCanCommit()) 
       { 
        scope.Complete(); 
       } 
      } 

    //workers 
    protected override void startWorker(DependentTransaction dependentTransaction) 
    { 
     Thread thread = new Thread(workerMethod); 
     thread.Start(dependentTransaction); 
    } 

    protected override void workerMethod(object transaction) 
    { 
     int executedStatements = 0; 
     DependentTransaction dependentTransaction; 
     dependentTransaction = transaction as DependentTransaction; 
     System.Diagnostics.Debug.Assert(dependentTransaction != null); //testing 
     try 
     { 
      //Transaction.Current = dependentTransaction; 
      using (TransactionScope scope = new TransactionScope(dependentTransaction)) 
      { 
       using (SqlConnection conn = new SqlConnection(this.GetConnectionString(this.parameters))) 
       { 
        /* Perform transactional work here */ 
        conn.Open(); 
        string statement = string.Empty; 
        using (SqlCommand cmd = conn.CreateCommand()) 
        { 

        } 
       } 
       //No errors-commit transaction 
       if (this.status == DBComponentStatus.Finished) 
       { 
        scope.Complete(); 
       } 
      } 
     } 
     catch (Exception e) 
     { 
      this.status = DBComponentStatus.Aborted; 
     } 
     finally 
     { 
      dependentTransaction.Complete(); 
      dependentTransaction.Dispose(); 
     } 
    } 
3

Es un poco tarde para la respuesta :) pero espero que sea útil para los demás. La respuesta contiene tres partes:

  1. ¿Qué significa "contexto de transacción en uso por otra sesión".
  2. Cómo reproducir el error "Contexto de transacción en uso por otra sesión".

1. ¿Qué significa "contexto de transacción en uso por otra sesión".

Aviso importante: el bloqueo del contexto de transacción se adquiere justo antes y se libera inmediatamente después de la interacción entre SqlConnection y SQL Server.

Cuando ejecuta alguna consulta SQL, SqlConnection "se ve" hay alguna transacción que lo ajuste. Puede ser SqlTransaction ("nativo" para SqlConnection) o Transaction del ensamblaje System.Transactions.

Cuando la transacción se encuentra SqlConnection lo usa para comunicarse con SQL Server y en el momento en que se comunican Transaction el contexto está bloqueado exclusivamente.

¿Qué significa TransactionScope? Crea Transaction y proporciona información sobre .NET Framework Components al respecto, de modo que todos, incluido SqlConnection, pueden (y por diseño deberían) usarlo.

Declarando TransactionScope estamos creando una nueva transacción que está disponible para todos los objetos "transaccionables" instanciados en el actual Thread. significa

error descrito los siguientes:

  1. Hemos creado varios SqlConnections bajo el mismo TransactionContext (lo que significa que relacionan a la misma transacción)
  2. Hemos hecho estas SqlConnection para comunicarse con SQL Server al mismo tiempo
  3. Uno de ellos bloqueó el contexto actual Transaction y el siguiente arrojó el error

2. Cómo reproducir el error "Contexto de transacción en uso por otra sesión".

En primer lugar, el contexto de transacción se utiliza ("bloqueado") justo en el momento de la ejecución del comando sql. Por lo tanto, es difícil reproducir dicho comportamiento con seguridad.

Pero podemos intentar hacerlo iniciando varios subprocesos ejecutando operaciones de SQL relativamente largas en una sola transacción. Vamos a preparar la mesa [dbo].[Persons] en [tests] Base de datos:

USE [tests] 
GO 
DROP TABLE [dbo].[Persons] 
GO 
CREATE TABLE [dbo].[Persons](
    [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, 
    [Name] [nvarchar](1024) NOT NULL, 
    [Nick] [nvarchar](1024) NOT NULL, 
    [Email] [nvarchar](1024) NOT NULL) 
GO 
DECLARE @Counter INT 
SET @Counter = 500 

WHILE (@Counter > 0) BEGIN 
    INSERT [dbo].[Persons] ([Name], [Nick], [Email]) 
    VALUES ('Sheev Palpatine', 'DarthSidious', '[email protected]') 
    SET @Counter = @Counter - 1 
END 
GO 

y reproducirse "Contexto de transacción en uso por otra sesión." de error con el código C# basado en Alcaudón ejemplo de código

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Transactions; 
using System.Data.SqlClient; 

namespace SO.SQL.Transactions 
{ 
    public static class TxContextInUseRepro 
    { 
     const int Iterations = 100; 
     const int ThreadCount = 10; 
     const int MaxThreadSleep = 50; 
     const string ConnectionString = "Initial Catalog=tests;Data Source=.;" + 
             "User ID=testUser;PWD=Qwerty12;"; 
     static readonly Random Rnd = new Random(); 
     public static void Main() 
     { 
      var txOptions = new TransactionOptions(); 
      txOptions.IsolationLevel = IsolationLevel.ReadCommitted; 
      using (var ctx = new TransactionScope(
       TransactionScopeOption.Required, txOptions)) 
      { 
       var current = Transaction.Current; 
       DependentTransaction dtx = current.DependentClone(
        DependentCloneOption.BlockCommitUntilComplete);    
       for (int i = 0; i < Iterations; i++) 
       { 
        // make the transaction distributed 
        using (SqlConnection con1 = new SqlConnection(ConnectionString)) 
        using (SqlConnection con2 = new SqlConnection(ConnectionString)) 
        { 
         con1.Open(); 
         con2.Open(); 
        } 

        var threads = new List<Thread>(); 
        for (int j = 0; j < ThreadCount; j++) 
        { 
         Thread t1 = new Thread(o => WorkCallback(dtx)); 
         threads.Add(t1); 
         t1.Start(); 
        } 

        for (int j = 0; j < ThreadCount; j++) 
         threads[j].Join(); 
       } 
       dtx.Complete(); 
       ctx.Complete(); 
      } 
     } 

     private static void WorkCallback(DependentTransaction dtx) 
     { 
      using (var txScope1 = new TransactionScope(dtx)) 
      { 
       using (SqlConnection con2 = new SqlConnection(ConnectionString)) 
       { 
        Thread.Sleep(Rnd.Next(MaxThreadSleep)); 
        con2.Open(); 
        using (var cmd = new SqlCommand("SELECT * FROM [dbo].[Persons]", con2)) 
        using (cmd.ExecuteReader()) { } // simply recieve data 
       } 
       txScope1.Complete(); 
      } 
     } 
    } 
} 

Y para concluir algunas palabras acerca de la implementación de soporte de transacciones en su aplicación:

  • Evitar las operaciones de datos multi-hilo si es posible (sin importar la carga o ahorro). P.ej. guarde SELECT/UPDATE/etc ... solicita en una sola cola y sírvelas con un trabajador de un solo hilo;
  • En aplicaciones de subprocesos múltiples, utilice transacciones. Siempre. En todos lados. Incluso para leer;
  • No comparta transacción única entre varios subprocesos. Se hace extraño, no obvio, trascendental y mensajes de error no reproducibles:
    • : múltiples interacciones simultáneas con el servidor bajo una transacción; "Contexto de transacción en uso por otra sesión."
    • "Excedió el tiempo de espera. El tiempo de espera transcurrido antes de la finalización de la operación o el servidor no responde": no se completaron las transacciones dependientes;
    • "La transacción está en duda.";
    • ... y supongo que un montón de otros ...
  • No se olvide de establecer nivel de aislamiento para TransactionScope.El valor predeterminado es Serializable, pero en la mayoría de los casos es suficiente ReadCommitted;
  • no se olvide de Completar() TransactionScope y DependentTransaction
0

Tengo una aplicación multi-hilo que hace algo de manipulación de datos y almacena los resultados en la base de datos. Debido a que diferentes subprocesos están trabajando en diferentes tipos de datos, escribir código para recopilar los resultados y eliminarlos de la base de datos en un subproceso es más engorroso que simplemente hacer que cada subproceso escriba los resultados cuando se completa.

Quería ejecutar esto en una transacción, por lo que tengo la opción de revertir todo el trabajo en caso de que ocurra un error en cualquiera de los subprocesos secundarios. Agregar transacciones comenzó a causar problemas, lo que me llevó a esta publicación, pero pude trabajar en ellas. El acceso a la base de datos de múltiples subprocesos en una sola transacción es posible. Incluso estoy usando LINQ-to-SQL y SqlBulkCopy juntos en la misma transacción.

La respuesta de Ilya Chidyakin fue muy útil. Debe pasar una Transacción dependiente a cada subproceso y utilizarla para crear un nuevo Aspecto de transacción. Y debe recordar comprometer tanto TransactionScope como DependentTransaction en cada thread. Finalmente, debe esperar para comprometer su transacción "original" hasta que todo el trabajo infantil haya finalizado. (DependentTransaction debería encargarse de esto, en realidad, pero ya estaba usando Thread.Join para esperar a que se termine todo el trabajo, antes de agregar transacciones a este proyecto).

La clave es que solo hay un hilo puede acceder a la base de datos en cualquier momento dado. Acabo de utilizar un semáforo para bloquear el acceso a la base de datos a un hilo a la vez. Como mis hilos pasan la mayor parte del tiempo computando y solo un poco de tiempo escribiendo en la base de datos, realmente no incurrí en una penalización de rendimiento debido a esto ... Sin embargo, si sus hilos están usando la base de datos con frecuencia, este requisito puede esencialmente, elimina el beneficio de rendimiento de multi-threading, si quieres todo lo contenido en una transacción.

Si tiene varios subprocesos accediendo a la base de datos a la vez, obtendrá una excepción con el mensaje "Contexto de transacción en uso por otra sesión". Si olvida realizar todas las transacciones en cada hilo, obtendrá una excepción con el mensaje "La transacción está en duda" cuando intente realizar la transacción más externa.

Cuestiones relacionadas