我正在寻找此错误根源的描述:“另一个会话正在使用的事务上下文”。
我有时会在我的一个单元测试中得到它,所以我无法提供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:事务存在疑问。 ---&GT; pired。操作完成之前经过的超时时间或服务器没有响应。
总结:我想了解“另一个会话使用的事务上下文”的含义以及如何重现它。
答案 0 :(得分:4)
回答有点迟了但希望它对其他人有用。 答案包含三个部分:
<强> 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
中实例化的对象。
描述的错误意味着以下内容:
SqlConnections
下创建了多个TransactionContext
(这意味着它们与同一笔交易相关)SqlConnection
同时与SQL Server进行通信Transaction
上下文,下一个锁定错误<强> 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 ...请求保存在单个队列中,并使用单线程工作程序为它们提供服务; TransactionScope
设置隔离级别。默认值为Serializable
,但在大多数情况下ReadCommitted
就足够了; TransactionScope
和DependentTransaction
答案 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;当你尝试提交最外面的事务时。