“事务上下文正在被另一个会话使用”的原因是什么?
我正在寻找此错误根源的描述:“另一个会话正在使用事务上下文”。
我有时会在我的一个单元测试中得到它,所以我无法提供重现代码。但我想知道错误的“设计”原因是什么。
更新:错误从 SQL Server 2008 返回为 SqlException。我收到错误的地方似乎是单线程的。但可能我有单元测试交互,因为我得到一次运行多个测试的错误(VS2008sp1 中的 MSTest)。 但失败的测试看起来像:
- 创建一个对象并将其保存在 DB-transaction (commit)
- create 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()
我找到了这些帖子:
- http://blogs.msdn.com/asiatech/archive/2009/08/10/system-transaction-may-fail-in-multiple-thread-environment.aspx
- http://msdn.microsoft.com/en-us/library/ff649002.aspx
但我无法理解“多个线程在事务范围内共享同一事务将导致以下异常:‘事务上下文正在被另一个会话使用。’”是什么意思。。所有的话都可以理解,但不是重点。
我实际上可以在线程之间共享系统事务。甚至还有特殊的机制 - DependentTransaction 类和 Transaction.DependentClone 方法。
我正在尝试重现第一篇文章中的用例:
- 主线程创建 DTC 事务,接收 DependentTransaction(在主线程上使用 Transaction.Current.DependentClone 创建)
- 子线程 1 通过创建基于依赖项的事务范围来加入此 DTC 事务事务(通过构造函数传递)
- 子线程 1 打开一个连接
- 子线程 2 通过基于依赖事务(通过构造函数传递)创建事务范围来加入 DTC 事务
- 子线程 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 的调用上失败但错误不同: 未处理的异常:System.Transactions.TransactionInDoubtException:事务有疑问。 ---> 厌倦了。操作完成之前超时时间已过,或者服务器未响应。
总结一下:我想了解“另一个会话正在使用的事务上下文”是什么意思以及如何重现它。
I'm looking for a description of the root of this error: "Transaction context in use by another session".
I get it sometimes in one of my unittests so I can't provider repro code. But I wonder what is "by design" reason for the error.
UPDATE: the error returns as SqlException from SQL Server 2008. A place where I get the error seems to be single-threaded. But probably I have unittests interaction as I get the error where run several tests at once (MSTest in VS2008sp1).
But the failing test looks like:
- create an object and save it inside DB-transaction (commit)
- create TransactionScope
- trying to open a connection - here I get SqlException with such 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()
I've found these posts:
- http://blogs.msdn.com/asiatech/archive/2009/08/10/system-transaction-may-fail-in-multiple-thread-environment.aspx
- http://msdn.microsoft.com/en-us/library/ff649002.aspx
But I can't understand what "Multiple threads sharing the same transaction in a transaction scope will cause the following exception: 'Transaction context in use by another session.'" means. All words are understandable but not the point.
I actually can share a system transaction between threads. And there is even special mechanism for this - DependentTransaction class and Transaction.DependentClone method.
I'm trying to reproduce a usecase from the first post:
- Main thread creates DTC transaction, receives DependentTransaction (created using Transaction.Current.DependentClone on the main thread
- Child thread 1 enlists in this DTC transaction by creating a transaction scope based on the dependent transaction (passed via constructor)
- Child thread 1 opens a connection
- Child thread 2 enlists in DTC transaction by creating a transaction scope based on the dependent transaction (passed via constructor)
- Child thread 2 opens a connection
with such code:
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);
}
}
It fails on Complete's call of root TransactionScope. But error is different:
Unhandled Exception: System.Transactions.TransactionInDoubtException: The transaction is in doubt. --->
pired. The timeout period elapsed prior to completion of the operation or the server is not responding.
To sum up: I want to understand what "Transaction context in use by another session" means and how to reproduce it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
回答有点晚了:)但希望对其他人有用。
答案包含三个部分:
1. “事务上下文正在被另一个会话使用”是什么意思。
重要提示:事务上下文锁在
SqlConnection
与 SQL Server 交互之前获取,并在交互之后立即释放。当您执行某些 SQL 查询时,
SqlConnection
“查看”是否有任何事务包装它。它可能是SqlTransaction
(SqlConnection 的“本机”)或来自System.Transactions
程序集的Transaction
。当事务发现时,
SqlConnection
使用它与 SQL Server 进行通信,并且在它们通信时,Transaction
上下文被独占锁定。TransactionScope
的作用是什么?它创建Transaction
并提供有关它的 .NET Framework 组件信息,因此包括 SqlConnection 在内的每个人都可以(并且按照设计应该)使用它。因此,声明
TransactionScope
我们正在创建新的 Transaction,它可用于当前Thread
中实例化的所有“可事务”对象。描述的错误含义如下:
TransactionContext
下创建了多个SqlConnections
(这意味着它们与同一事务相关)。SqlConnection
同时与 SQL Server 通信,Transaction
上下文,下一个抛出错误2。如何重现错误“事务上下文正在被另一个会话使用。”
首先,事务上下文是在 sql 命令执行时使用(“锁定”)的。因此,很难重现这种行为。
但我们可以尝试通过在单个事务下启动多个线程运行相对较长的 SQL 操作来实现。
让我们在
[tests]
数据库中准备表[dbo].[Persons]
:并重现“另一个会话正在使用的事务上下文”。基于 Shrike 代码示例的 C# 代码错误
最后,关于在应用程序中实现事务支持的几句话:
SELECT
/UPDATE
/等...请求保存在单个队列中,并使用单线程工作器为它们提供服务;TransactionScope
设置隔离级别。默认为Serializable
,但在大多数情况下,ReadCommited
就足够了;TransactionScope
和DependentTransaction
It's a bit late for answer :) but hope it will be useful for others.
Answer contains three parts:
1. What does it mean "Transaction context in use by another session."
Important notice: Transaction context lock is acquired just before and released immediately after interaction between
SqlConnection
and SQL Server.When you execute some SQL Query,
SqlConnection
"looks" is there any transaction wrapping it. It may beSqlTransaction
("native" for SqlConnection) orTransaction
fromSystem.Transactions
assembly.When transaction found
SqlConnection
uses it to communicate with SQL Server and at the moment they communicateTransaction
context is exclusively locked.What does
TransactionScope
? It createsTransaction
and provides .NET Framework Components infromation about it, so everyone including SqlConnection can (and by design should) use it.So declaring
TransactionScope
we're creating new Transaction which is available to all "transactable" objects instantiated in currentThread
.Described error means the following:
SqlConnections
under the sameTransactionContext
(which means they related to the same transaction)SqlConnection
to communicate with SQL Server simultaneouslyTransaction
context and next one throwed error2. How to reproduce error "Transaction context in use by another session."
First of all, transaction context is used ("locked") right at the time of sql command execution. So it's difficult to reproduce such a behavior for sure.
But we can try to do it by starting multiple threads running relatively long SQL operations under the single transaction.
Let's prepare table
[dbo].[Persons]
in[tests]
Database:And reproduce "Transaction context in use by another session." error with C# code based on Shrike code example
And in conclusion a few words about implementing transaction support in your application:
SELECT
/UPDATE
/ etc... requests in a single queue and serve them with a single-thread worker;TransactionScope
. Default isSerializable
but in most casesReadCommitted
is enough;TransactionScope
andDependentTransaction
两个连接上发出命令,则可能会发生冲突。
换句话说,一个线程正在发出命令一个连接上的命令并在事务上下文上持有某种锁定,使用另一个连接的另一个线程尝试同时执行命令,并且无法锁定另一个线程正在使用的同一事务上下文。 。
Sounds pretty straightforward. If you enlist two different connections in the same transaction, then try to issue commands on each of the two connections, simultaneously, from different threads, a conflict could occur.
In other words, one thread is issuing a command on one connection and holds some kind of lock on the transaction context. The other thread, using the other connection, tries to execute commands at the same time, and cannot lock the same transaction context, which is being used by the other thread.
退后一步,更多地关注您的代码,而不是周围的多线程信息。
如果您的场景不涉及线程,则它可能与未按您期望的方式关闭的部分相关。
也许您正在调用的 sql 代码未到达该提交事务指令。或者该级别还涉及其他内容。也许您在 .net 代码中使用了 SqlConnection 实例设置事务,并且在使用 TransactionScope 的其他代码上重用了同一实例。尝试在适当的地方添加 using() 指令,以确保所有内容都按您的预期关闭。
Take a step back and focus more on your code and less in the multiple threads info floating around.
If your scenario doesn't involve threading, it might relate to pieces that aren't closed as you expect it to.
Maybe the sql code you are calling doesn't reach that commit transaction instruction. Or there is something else involved at that level. Maybe you used a SqlConnection instance setting the transaction in the .net code, and are reusing that same instance on the other code that uses the TransactionScope. Try adding using() instructions where appropriate, to make sure everything is closed as you expect it.
您必须为每个线程创建一个 DependentTransaction ,然后在线程内创建并创建一个 DependentTransaction 。使用构造函数中的
dependentTransaction
打开 TransaccionScope 内的数据库连接。You must create a
DependentTransaction
for each thread an then inside the thread create & open the db connection inside aTransacctionScope
using thedependentTransaction
in the ctor.在使用多个对象构建 Linq 语句时,我将如何处理这个问题,即为每个类提供一个构造函数,该构造函数接受数据上下文,并在每个类中提供一个相应的 GetDataContext() 方法。组合类时,我会更新传入第一个类的 GetContext() 的类实例
How I'd deal with that issue when building Linq statements with mutlipe objects is to have a constructor for each class that takes in a data context and a coresponding GetDataContext() method in each class. when combining classes, I'd new up the class instances passing in the first class's GetContext()
我有一个多线程应用程序,它执行一些数据操作并将结果存储在数据库中。由于不同的线程处理不同类型的数据,因此编写代码来收集结果并将其在一个线程中刷新到数据库比让每个线程在完成后自行将结果写入要麻烦。
我想在事务中运行它,以便在任何一个子线程中发生错误时我可以选择恢复所有工作。添加交易开始引起问题,这导致我发布了这篇文章,但我能够解决这些问题。单个事务中的多线程数据库访问是可能的。我什至在同一个事务中同时使用 LINQ-to-SQL 和 SqlBulkCopy。
我发现 Ilya Chidyakin 的回答非常有帮助。您需要将 DependentTransaction 传递给每个线程,并使用它来创建新的 TransactionScope。并且,您需要记住在每个线程中提交 TransactionScope 和 DependentTransaction。最后,您必须等待所有子工作完成后才能提交“原始”事务。 (实际上,DependentTransaction 应该解决这一问题,但在将事务添加到该项目之前,我已经使用 Thread.Join 等待所有工作完成。)
关键是,只有一个线程可以访问任何给定时间的数据库。我只是使用信号量来阻止一次一个线程对数据库的访问。由于我的线程大部分时间都花在计算上,只有一点点时间写入数据库,因此我并没有真正遭受性能损失......但是,如果您的线程经常使用数据库,则此要求可能会如果您希望所有内容都包含在一个事务中,则从本质上消除多线程的性能优势。
如果您有多个线程同时访问数据库,您将收到一个异常,并显示消息“事务上下文正在被另一个会话使用”。如果您忘记提交每个线程中的所有事务,则当您尝试提交最外层事务时,您将收到一个异常,并显示消息“事务存在疑问”。
I have a multi-threaded application that does some data manipulation and stores the results in the database. Because different threads are working on different types of data, writing code to collect the results and flush it out to the database in one thread is more cumbersome than just having each thread write the results out itself when it is done.
I wanted to run this in a transaction, so that I have the option to revert all of the work in the event that an error happens in any one of the child threads. Adding transactions started causing problems, which led me to this posting, but I was able to work through them. Multi-threaded database access in a single transaction is possible. I'm even using both LINQ-to-SQL and SqlBulkCopy together in the same transaction.
I found Ilya Chidyakin's answer to be very helpful. You need to pass a DependentTransaction to each thread, and use that to create a new TransactionScope. And, you need to remember to commit both the TransactionScope and the DependentTransaction in each thread. Finally, you must wait to commit your "original" transaction until all of the child work is done. (DependentTransaction should take care of this, actually, but I was already using Thread.Join to wait for all of the work to be done, before I added transactions to this project.)
The key thing is, only one thread can be accessing the database at any given time. I just used a semaphore to block access to the database to one thread at a time. Since my threads spend most of the time computing and only a little bit of time writing to the database, I didn't really incur a performance penalty because of this... However, if your threads are using the database frequently, this requirement may essentially remove the performance benefit of multi-threading, if you want everything contained in one transaction.
If you have multiple threads accessing the database at once, you will get an Exception with the message "Transaction context in use by another session." If you forget to commit all of the transactions in each thread, you will get an Exception with the message "The transaction is in doubt" when you try to commit the outer-most transaction.