在什么情况下SqlConnection会自动被征召到环境事务范围事务中?

在事务中“征募”一个SqlConnection意味着什么?这仅仅意味着我在连接上执行的命令将参与事务吗?

如果是,在什么情况下SqlConnection 自动被编入环境事务范围事务?

参见代码注释中的问题。我对每个问题的答案的猜测在每个问题后面的括号里。

场景1:在事务范围内打开连接

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{
// Q1: Is connection automatically enlisted in transaction? (Yes?)
//
// Q2: If I open (and run commands on) a second connection now,
// with an identical connection string,
// what, if any, is the relationship of this second connection to the first?
//
// Q3: Will this second connection's automatic enlistment
// in the current transaction scope cause the transaction to be
// escalated to a distributed transaction? (Yes?)
}

场景2:在事务范围内使用在事务范围外打开的连接

//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
// Connection was opened before transaction scope was created
// Q4: If I start executing commands on the connection now,
// will it automatically become enlisted in the current transaction scope? (No?)
//
// Q5: If not enlisted, will commands I execute on the connection now
// participate in the ambient transaction? (No?)
//
// Q6: If commands on this connection are
// not participating in the current transaction, will they be committed
// even if rollback the current transaction scope? (Yes?)
//
// If my thoughts are correct, all of the above is disturbing,
// because it would look like I'm executing commands
// in a transaction scope, when in fact I'm not at all,
// until I do the following...
//
// Now enlisting existing connection in current transaction
conn.EnlistTransaction( Transaction.Current );
//
// Q7: Does the above method explicitly enlist the pre-existing connection
// in the current ambient transaction, so that commands I
// execute on the connection now participate in the
// ambient transaction? (Yes?)
//
// Q8: If the existing connection was already enlisted in a transaction
// when I called the above method, what would happen?  Might an error be thrown? (Probably?)
//
// Q9: If the existing connection was already enlisted in a transaction
// and I did NOT call the above method to enlist it, would any commands
// I execute on it participate in it's existing transaction rather than
// the current transaction scope. (Yes?)
}
35320 次浏览

问了这个问题之后,我做了一些测试,我自己找到了大部分答案,因为没有其他人回答。如果我漏了什么请告诉我。

Q1:在事务中是否自动登记连接?

是的,除非在连接字符串中指定了enlist=false。连接池找到一个可用的连接。可用连接是指没有在事务中登记的连接或在同一事务中登记的连接。

Q2:如果我现在打开(并运行命令)第二个连接,使用相同的连接字符串,如果有的话,这第二个连接与第一个连接的关系是什么?

第二个连接是一个独立的连接,它参与同一个事务。我不确定这两个连接上命令的交互,因为它们是针对同一个数据库运行的,但我认为如果同时在两个连接上发出命令,可能会发生错误:像“另一个会话正在使用事务上下文”;这样的错误

Q3:此第二个连接在当前事务范围中的自动征召是否会导致事务升级为分布式事务?

是的,它被升级为分布式事务,因此招募多个连接,即使使用相同的连接字符串,也会导致它成为分布式事务,这可以通过检查Transaction.Current.TransactionInformation.DistributedIdentifier的非空GUID来确认。

* 更新:我在某处读到,这在SQL Server 2008中是固定的,所以当相同的连接字符串用于两个连接时(只要两个连接没有同时打开),MSDTC就不会被使用。这允许您在一个事务中多次打开和关闭连接,这样可以通过尽可能晚地打开连接和尽快地关闭连接来更好地利用连接池。

Q4:如果我现在开始在连接上执行命令,它会自动被征召到当前事务范围中吗?

不。在没有活动事务作用域时打开的连接将不会自动征调到新创建的事务作用域中。

Q5:如果没有注册,我在连接上执行的命令现在会参与环境事务吗?

不。除非您在事务范围内打开连接,或在该范围内登记现有连接,否则基本上没有事务。您的连接必须自动或手动地征召到事务范围中,以便您的命令能够参与事务。

Q6:如果此连接上的命令没有参与当前事务,即使回滚当前事务范围,它们也会被提交吗?

是的,不参与事务的连接上的命令在发出时被提交,即使代码恰好在回滚的事务范围块中执行。如果连接没有被征召到当前事务作用域中,那么它就没有参与事务,因此提交或回滚事务对没有被征召到事务作用域中的连接上发出的命令没有影响……这个人发现。这是一个很难发现的问题,除非你理解自动征募过程:它只发生在连接被打开时。

问题7:上面的方法是否显式地在当前环境事务中使用预先存在的连接,以便我在连接上执行的命令现在参与环境事务?

是的。可以通过调用EnlistTransaction(Transaction.Current)显式地在当前事务作用域中征召现有连接。您还可以通过使用DependentTransaction在事务中的单独线程上注册连接,但像以前一样,我不确定同一事务中涉及到同一数据库的两个连接如何交互…并且可能会发生错误,当然第二个已登记的连接会导致事务升级为分布式事务。

Q8:如果当我调用上面的方法时,现有的连接已经在事务中登记,会发生什么?是否会抛出错误?

可能会抛出错误。如果使用了TransactionScopeOption.Required,并且连接已经在事务范围事务中登记,则没有错误;事实上,没有为作用域创建新的事务,并且事务计数(@@trancount)没有增加。然而,如果你使用TransactionScopeOption.RequiresNew,那么当你试图在新的事务范围中登记连接时,你会得到一个有用的错误消息:"连接当前已登记事务。完成当前事务后重试”;是的,如果您完成了登记该连接的事务,则可以安全地在新事务中登记该连接。

* 更新:如果你之前在连接上调用了__ABC0,当你试图在一个新的事务范围事务中登记时,会抛出一个稍微不同的错误:不能登记在事务中,因为连接上的一个本地事务正在进行中。完成本地事务后重试”;另一方面,当__ABC2在事务作用域事务中被征用时,你可以安全地在__ABC2上调用__ABC0,这实际上会使@@trancount增加1,不像使用嵌套事务作用域的Required选项那样,它不会导致它增加。有趣的是,如果你继续使用__ABC4选项创建另一个嵌套事务范围,你将不会得到一个错误,因为已经有一个活动的事务范围事务不会发生任何变化(记住,当事务范围事务已经活动并且使用__ABC4选项时,@@trancount不会增加)。

Q9:如果现有的连接已经被征召到事务中,而我没有调用上面的方法来征召它,那么我在它上执行的任何命令都将参与到它现有的事务中而不是当前的事务范围中吗?

是的。不管在c#代码中活动事务的作用域是什么,命令都要参与连接所登记的任何事务。

做得好,Triynko,你的答案在我看来都很准确和完整。我还想指出一些事情:

(1)手动征兵

在上面的代码中,你(正确地)显示了像这样的手动征兵:

using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
using (TransactionScope ts = new TransactionScope())
{
conn.EnlistTransaction(Transaction.Current);
}
}

但是,也可以这样做,在连接字符串中使用Enlist=false。

string connStr = "...; Enlist = false";
using (TransactionScope ts = new TransactionScope())
{
using (SqlConnection conn1 = new SqlConnection(connStr))
{
conn1.Open();
conn1.EnlistTransaction(Transaction.Current);
}


using (SqlConnection conn2 = new SqlConnection(connStr))
{
conn2.Open();
conn2.EnlistTransaction(Transaction.Current);
}
}

这里还有一件事需要注意。当conn2被打开时,连接池代码不知道您以后想要在与conn1相同的事务中登记它,这意味着conn2被赋予了与conn1不同的内部连接。然后,当conn2被征募时,现在有2个连接被征募,因此必须将事务提升到MSDTC。这种提升只能通过使用自动入伍来避免。

在。net 4.0之前,我强烈建议设置连接字符串中的"Transaction Binding=Explicit Unbind"。这个问题在。net 4.0中被修复,使得显式解绑定完全没有必要。

(3)滚动你自己的CommittableTransaction并将Transaction.Current设置为它本质上与TransactionScope所做的事情相同。这实际上很少有用,仅供参考。

Transaction.Current是线程静态的。这意味着Transaction.Current只在创建TransactionScope的线程上设置。因此,多个线程执行相同的TransactionScope(可能使用Task)是不可能的。

我们看到的另一个奇怪的情况是,如果你构造了一个EntityConnectionStringBuilder,它将与TransactionScope.Current混淆,并(我们认为)加入事务。我们已经在调试器中观察到这一点,其中TransactionScope.Currentcurrent.TransactionInformation.internalTransaction在构造之前显示enlistmentCount == 1,在构造之后显示enlistmentCount == 2

为了避免这种情况,在内部构建它

using (new TransactionScope(TransactionScopeOption.Suppress)) < /代码> < / p >

而且可能超出了您的操作范围(我们每次需要连接时都在构建它)。