【问题标题】:What is the reason of "Transaction context in use by another session"“另一个会话正在使用事务上下文”的原因是什么
【发布时间】:2010-05-18 15:36:54
【问题描述】:

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

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

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

  • 创建一个对象并将其保存在 DB-transaction(提交)中
  • 创建 TransactionScope
  • 试图打开一个连接 - 在这里我得到了带有这样的堆栈跟踪的 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()

我找到了这些帖子:

但我不明白“在事务范围内共享同一事务的多个线程会导致以下异常:'Transaction context in use by another session.'” 是什么意思。所有的话都可以理解,但不是重点。

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

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

  1. 主线程创建DTC事务,接收DependentTransaction(在主线程上使用Transaction.Current.DependentClone创建
  2. 子线程 1 通过基于依赖事务(通过构造函数传递)创建事务范围来加入此 DTC 事务
  3. 子线程 1 打开一个连接
  4. 子线程 2 通过基于依赖事务(通过构造函数传递)创建事务范围来加入 DTC 事务
  5. 子线程 2 打开一个连接

使用这样的代码:

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

它在 Complete 调用根 TransactionScope 时失败。但错误是不同的: 未处理的异常:System.Transactions.TransactionInDoubtException:事务有问题。 ---> 已过期。在操作完成之前超时时间已过或服务器没有响应。

总结一下:我想了解“另一个会话正在使用的事务上下文”是什么意思以及如何重现它。

【问题讨论】:

  • 一个小问题:你确定你有分布式事务吗?你建立了 2 个开放的连接: con1.Open(); con2.Open();但是连接字符串是相同的,并且您使用的是 Sql2008。 AFAIK 如果您使用 Sql2008 和相同的连接字符串,则事务不会升级为分布式。它仍然是“本地的”。我的 2 美分。
  • 不,同时打开两个连接总是会导致分布式事务。它们是否具有相同的连接字符串并不重要
  • @Fabrizio,但如果同一个连接实例连续打开和关闭,那么在 sql2008 中我们得到本地 tran 但在 sql2005 中是分布式 tran。

标签: .net sql-server transactions transactionscope


【解决方案1】:

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

  1. “另一个会话正在使用事务上下文”是什么意思。
  2. 如何重现错误“另一个会话正在使用事务上下文。”

1. “另一个会话正在使用事务上下文”是什么意思。

重要提示:事务上下文锁在SqlConnection 与SQL Server 交互之前获取并立即释放。

当您执行一些 SQL 查询时,SqlConnection“看起来”是否有任何事务包装它。它可能是SqlTransaction(SqlConnection 的“本机”)或来自System.Transactions 程序集的Transaction

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

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

因此,声明 TransactionScope 我们正在创建新的 Transaction,它可用于在当前 Thread 中实例化的所有“可交易”对象。

描述的错误意思如下:

  1. 我们在同一个 TransactionContext 下创建了多个 SqlConnections(这意味着它们与同一个事务相关)
  2. 我们要求这些SqlConnection 同时与 SQL Server 通信
  3. 其中一个锁定当前Transaction 上下文,下一个抛出错误

2。如何重现错误“另一个会话正在使用事务上下文。”

首先,在执行 sql 命令时使用(“锁定”)事务上下文。所以很难肯定地重现这种行为。

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

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

并重现“另一个会话正在使用的事务上下文”。基于 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/ 等请求保存在单个队列中,并使用单线程工作器为它们提供服务;
  • 在多线程应用程序中使用事务。总是。到处。即使是阅读;
  • 不要在多个线程之间共享单个事务。它会导致奇怪的、不明显的、超验的和不可重现的错误消息:
    • “另一个会话正在使用事务上下文。”:在一个事务下与服务器的多个同时交互;
    • “超时已过。在操作完成之前超时时间已过或服务器没有响应。”:不依赖事务已完成;
    • “交易有疑问。”;
    • ...我假设还有很多其他...
  • 不要忘记为TransactionScope 设置隔离级别。默认是Serializable,但在大多数情况下ReadCommitted 就足够了;
  • 别忘了 Complete() TransactionScopeDependentTransaction

【讨论】:

    【解决方案2】:

    "多个线程共享同一个 事务范围内的事务 将导致以下异常: '另一个正在使用的事务上下文 会话。'"

    听起来很简单。如果您在同一个事务中加入两个不同的连接,然后尝试同时从不同的线程对这两个连接中的每一个发出命令,则可能会发生冲突。

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

    【讨论】:

    • 这个阶段很简单,但不是重点。我展示了一个使用 System.Transactions 的多线程示例。而且没有这样的错误(但还有另一个错误)。另外看看SysTrans团队的一个人的这篇文章(我相信):pluralsight-training.net/community/blogs/jimjohn/archive/2005/…
    • 如果 SysTrans 不支持多线程环境,为什么我们需要 DependantTransaction 类型。
    【解决方案3】:

    退后一步,更多地关注您的代码,而不是四处飘荡的多线程信息。

    如果您的场景不涉及线程,则它可能与未按预期关闭的部分有关。

    也许您调用的 sql 代码没有达到该提交事务指令。或者在那个级别还涉及其他一些事情。也许您使用 SqlConnection 实例在 .net 代码中设置事务,并在使用 TransactionScope 的其他代码上重用相同的实例。尝试在适当的地方添加 using() 指令,以确保一切都按预期关闭。

    【讨论】:

      【解决方案4】:

      在使用多个对象构建 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;
              }
      
      }
      

      【讨论】:

        【解决方案5】:

        您必须为每个线程创建一个DependentTransaction,然后在线程内部使用ctor 中的dependentTransaction 创建并打开TransacctionScope 内的数据库连接。

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

        【讨论】:

          【解决方案6】:

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

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

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

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

          如果您有多个线程同时访问数据库,您将收到带有消息“另一个会话正在使用事务上下文”的异常。如果您忘记提交每个线程中的所有事务,当您尝试提交最外层事务时,您将收到一条带有“事务存在疑问”消息的异常。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-05-15
            • 1970-01-01
            • 2018-01-10
            • 2013-06-23
            • 1970-01-01
            • 2014-09-10
            • 2023-03-14
            • 2017-04-10
            相关资源
            最近更新 更多