“另一个会话使用的事务上下文”的原因是什么

时间:2010-05-18 15:36:55

标签: .net sql-server transactions transactionscope

我正在寻找此错误根源的描述:“另一个会话正在使用的事务上下文”。

我有时会在我的一个单元测试中得到它,所以我无法提供repro代码。但我想知道错误的原因是“按设计”是什么原因。

更新:错误从SQL Server 2008返回为SqlException。我收到错误的地方似乎是单线程的。但是我可能有单元测试交互,因为我得到了错误,一次运行几个测试(VS2008sp1中的MSTest)。 但失败的测试看起来像:

  • 创建一个对象并将其保存在DB-transaction(commit)
  • 创建TransactionScope
  • 尝试打开一个连接 - 这里我得到了带有这种stacktrace的SqlException:

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

我发现了这些帖子:

但我无法理解“在事务范围内共享同一事务的多个线程将导致以下异常:'另一个会话正在使用的事务上下文。'”表示。所有的话都是可以理解的,但不是重点。

我实际上可以在线程之间共享系统事务。甚至还有一种特殊的机制 - DependentTransaction类和Transaction.DependentClone方法。

我正在尝试从第一篇文章中重现一个用例:

  1. 主线程创建DTC事务,接收DependentTransaction(在主线程上使用Transaction.Current.DependentClone创建
  2. 子线程1通过基于依赖事务创建事务范围(通过构造函数传递)来参与此DTC事务
  3. 子线程1打开连接
  4. 子线程2通过基于依赖事务创建事务范围(通过构造函数传递)在DTC事务中登记
  5. 子线程2打开连接
  6. 使用此类代码:

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

    完成对根TransactionScope的调用失败。但错误是不同的: 未处理的异常:System.Transactions.TransactionInDoubtException:事务存在疑问。 ---&GT; pired。操作完成之前经过的超时时间或服务器没有响应。

    总结:我想了解“另一个会话使用的事务上下文”的含义以及如何重现它。

6 个答案:

答案 0 :(得分:4)

回答有点迟了但希望它对其他人有用。 答案包含三个部分:

  1. 它是什么意思&#34;另一个会话使用的交易上下文。&#34;
  2. 如何重现错误&#34;另一个会话正在使用的交易上下文。&#34;
  3. <强> 1。它是什么意思&#34;另一个会话使用的交易上下文。&#34;

    重要提示:事务上下文锁定是在SqlConnection和SQL Server之间的交互之后立即获取并立即释放的。

    执行某些SQL查询时,SqlConnection&#34;看起来&#34;是否有任何包装它的交易。它可能是SqlTransaction(&#34;本地&#34;对于SqlConnection)或Transaction来自System.Transactions程序集。

    当找到的事务SqlConnection使用它与SQL Server进行通信时,他们通信时Transaction上下文被独占锁定。

    TransactionScope是什么?它创建Transaction并提供有关它的.NET Framework组件信息,因此包括SqlConnection在内的每个人都可以(并且设计应该)使用它。

    声明TransactionScope我们正在创建一个可供所有人使用的新交易&#34;可交易的&#34;在当前Thread中实例化的对象。

    描述的错误意味着以下内容:

    1. 我们在同一SqlConnections下创建了多个TransactionContext(这意味着它们与同一笔交易相关)
    2. 我们要求这些SqlConnection同时与SQL Server进行通信
    3. 其中一个锁定当前Transaction上下文,下一个锁定错误
    4. <强> 2。如何重现错误&#34;另一个会话使用的交易上下文。&#34;

      首先,在sql命令执行时使用事务上下文(&#34;锁定&#34;)。因此,很难重现这种行为。

      但是我们可以通过在单个事务下启动运行相对较长的SQL操作的多个线程来尝试这样做。 让我们在[dbo].[Persons]数据库中准备表[tests]

      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', 'spalpatine@galaxyempire.gov')
          SET @Counter = @Counter - 1
      END
      GO
      

      并重现&#34;另一个会话正在使用的交易上下文。&#34;基于Shrike代码示例的C#代码出错

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

      最后,在应用程序中实现事务支持的几句话:

      • 尽可能避免多线程数据操作(无论是加载还是保存)。例如。将SELECT / UPDATE / etc ...请求保存在单个队列中,并使用单线程工作程序为它们提供服务;
      • 在多线程应用程序中使用事务。总是。到处。即使是阅读;
      • 不要在多个线程之间共享单个事务。它会导致奇怪的,不明显的,超然的不可重现的错误消息:
        • &#34;另一个会话正在使用的事务上下文。&#34;:在一个事务下与服务器进行多个同时交互;
        • &#34;超时已过期。操作完成之前经过的超时时间或服务器没有响应。&#34;:未完成依赖事务;
        • &#34;交易存在疑问。&#34;;
        • ......我假设其他很多......
      • 别忘了为TransactionScope设置隔离级别。默认值为Serializable,但在大多数情况下ReadCommitted就足够了;
      • 不要忘记完成()TransactionScopeDependentTransaction

答案 1 :(得分:2)

  

“多个线程共享相同的内容   事务范围内的事务   将导致以下异常:   '另一个人使用的交易环境   会话'“

听起来很简单。如果在同一事务中登记两个不同的连接,则尝试在两个连接中的每个连接上发出命令,同时从不同的线程发生冲突。

换句话说,一个线程在一个连接上发出命令,并在事务上下文中保持某种锁定。另一个线程使用另一个连接尝试同时执行命令,并且无法锁定另一个线程正在使用的相同事务上下文。

答案 2 :(得分:1)

退后一步,更多地关注你的代码,减少多线程信息的浮动。

如果您的方案不涉及线程,则可能与未按预期关闭的碎片有关。

您调用的SQL代码可能无法访问该提交事务指令。或者在该级别涉及其他内容。也许你使用了一个SqlConnection实例来设置.net代码中的事务,并且在使用TransactionScope的其他代码上重用相同的实例。尝试在适当的时候添加using()指令,以确保所有内容都按预期关闭。

答案 3 :(得分:0)

在使用mutlipe对象构建Linq语句时,如何处理该问题的方法是为每个类创建一个构造函数,该构造函数接受每个类中的数据上下文和相应的GetDataContext()方法。在组合类时,我会在第一个类的GetContext()

中传递类实例
  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;
        }

}

答案 4 :(得分:0)

你必须为每个线程创建一个DependentTransaction然后在线程创建&amp;使用ctor中的TransacctionScope打开dependentTransaction内的数据库连接。

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

答案 5 :(得分:0)

我有一个多线程应用程序,它执行一些数据操作并将结果存储在数据库中。因为不同的线程正在处理不同类型的数据,所以编写代码来收集结果并将其刷新到一个线程中的数据库比让每个线程在结束时自己写出结果更麻烦。

我想在一个事务中运行它,这样我就可以选择在任何一个子线程中发生错误时恢复所有工作。添加交易开始引起问题,这导致我发布这个帖子,但我能够解决这些问题。可以在单个事务中进行多线程数据库访问。我甚至在同一个事务中同时使用LINQ-to-SQL和SqlBulkCopy。

我发现Ilya Chidyakin的回答非常有帮助。您需要将DependentTransaction传递给每个线程,并使用它来创建新的TransactionScope。而且,您需要记住在每个线程中提交TransactionScope和DependentTransaction。最后,你必须等待提交你原来的&#34;交易直到所有子工作完成。 (实际上,DependentTransaction应该处理这个问题,但在我将事务添加到此项目之前,我已经在使用Thread.Join等待所有工作完成。)

关键是,在任何给定时间只有一个线程可以访问数据库。我只是使用信号量来阻止一次访问数据库到一个线程。由于我的线程大部分时间花在计算上并且只花了一点时间写入数据库,因此我没有真正产生性能损失......但是,如果你的线程经常使用数据库,这个如果您希望一个事务中包含所有内容,则需求可能实质上消除多线程的性能优势。

如果您有多个线程同时访问数据库,您将获得一个异常,其中包含另一个会话使用的消息&#34; Transaction上下文。&#34;如果您忘记提交每个线程中的所有事务,您将收到一条带有消息的异常&#34;该事务存在疑问&#34;当你尝试提交最外面的事务时。