Shr*_*ike 14 .net sql-server transactions transactionscope
我正在寻找这个错误根源的描述:"另一个会话正在使用的事务上下文".
我有时会在我的一个单元测试中得到它,所以我不能提供repro代码.但我想知道什么是"按设计"错误的原因.
更新:错误从SQL Server 2008返回为SqlException.我收到错误的地方似乎是单线程的.但是我可能有单元测试交互,因为我得到了错误,一次运行几个测试(VS2008sp1中的MSTest).但失败的测试看起来像:
.
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方法.
我试图从第一篇文章中重现一个用例:
用这样的代码:
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:该事务存在疑问.--->很高兴.操作完成之前经过的超时时间或服务器没有响应.
总结一下:我想了解"另一个会话使用的事务上下文"是什么意思以及如何重现它.
小智 5
回答:)有点晚了,但希望对其他人有用。答案包含三个部分:
1.什么意思是“另一个会话正在使用事务上下文”。
重要说明:事务上下文锁是在SqlConnection与SQL Server 之间进行交互之前获取的,并在与SQL Server 之间进行交互后立即释放。
当您执行某些SQL查询时,SqlConnection“查找”会包含任何包装它的事务。它可能是SqlTransaction(对于SqlConnection为“本地”)或Transaction来自System.Transactions程序集。
找到事务后,便SqlConnection使用它与SQL Server进行通信,此刻,它们之间的通信Transaction上下文被完全锁定。
这是什么TransactionScope?它创建Transaction并提供有关它的.NET Framework组件信息,因此包括SqlConnection在内的每个人都可以(并且在设计上应使用)它。
因此,声明TransactionScope我们正在创建新的Transaction,该事务可用于在current中实例化的所有“可交易”对象Thread。
描述的错误表示以下内容:
SqlConnections相同的条件下创建了多个TransactionContext(这意味着它们与同一笔交易相关)SqlConnection同时与SQL Server通信Transaction上下文,下一个引发了错误2.如何重现错误“另一个会话正在使用的事务上下文”。
首先,在执行sql命令时立即使用(“锁定”)事务上下文。因此,很难肯定地重现这种行为。
但是我们可以尝试通过在单个事务下启动多个运行相对较长的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
并重现“另一个会话正在使用的事务上下文”。基于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 ...请求保存在单个队列中,并使用单线程工作程序处理这些请求;TransactionScope。默认值是,Serializable但是在大多数情况下ReadCommitted足够了;TransactionScope和DependentTransaction“多个线程在事务范围内共享同一事务将导致以下异常:‘事务上下文正在被另一个会话使用。’”
听起来很简单。如果您在同一事务中登记两个不同的连接,然后尝试从不同的线程同时对这两个连接发出命令,则可能会发生冲突。
换句话说,一个线程在一个连接上发出命令,并在事务上下文上持有某种锁。使用另一个连接的另一个线程尝试同时执行命令,并且无法锁定另一个线程正在使用的同一事务上下文。
| 归档时间: | 
 | 
| 查看次数: | 22606 次 | 
| 最近记录: |