在什么情况下SqlConnection会自动加入环境TransactionScope事务中?


201

SqlConnection在事务中被“征募”是什么意思?这是否仅表示我在连接上执行的命令将参与事务?

如果是这样,在什么情况下SqlConnection会自动加入环境TransactionScope事务中?

查看代码注释中的问题。我对每个问题答案的猜测都跟在括号中的每个问题之后。

方案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?)
}

Answers:


188

自问这个问题以来,我进行了一些测试,并且我自己找到了大多数(如果不是全部)答案,因为没有其他人回答。如果我错过了任何事情,请告诉我。

Q1。是的,除非在连接字符串中指定了“ enlist = false”。连接池找到可用的连接。可用连接是未参与事务的连接或已参与同一事务的连接。

Q2。第二个连接是一个独立的连接,它参与同一事务。我不知道关于这两个连接命令的作用,因为他们是针对同一数据库上运行,但我认为,如果命令是在同一时间上都发出了可能发生的错误:错误一样在使用“事务上下文另一个会议”

Q3。是的,它升级为分布式事务,因此即使使用相同的连接字符串,加入一个以上的连接也会导致它成为分布式事务,可以通过在Transaction.Current.TransactionInformation中检查非null GUID来确认.DistributedIdentifier。*更新:我读到某处该问题在SQL Server 2008中已得到修复,因此当两个连接使用相同的连接字符串时,只要不同时打开两个连接,就不会使用MSDTC。这样一来,您就可以在事务中打开连接并多次关闭它,这可以通过尽可能晚地打开连接并尽快关闭它们来更好地利用连接池。

Q4。否。没有事务作用域处于活动状态时打开的连接将不会自动加入新创建的事务作用域中。

Q5。否。除非您在事务作用域中打开一个连接或在作用域中注册一个现有连接,否则基本上没有交易。您的连接必须自动或手动加入事务范围中,以便命令参与事务。

Q6。是的,即使代码恰好在已回滚的事务作用域块中执行,也不参与事务的连接上的命令也会按已提交的方式提交。如果连接不在当前事务范围内征,它不参与交易,所以提交或回滚事务将会对交易范围没有征用的连接发出的命令没有影响......因为这家伙发现。这是一个很艰巨察觉,除非你理解了自动征用过程:它仅在连接打开时里面一个活跃的事务范围。

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

Q8。可能会引发错误。如果使用TransactionScopeOption.Required,并且该连接已经在事务范围事务中登记,则没有错误;实际上,没有为该范围创建新的事务,并且事务计数(@@ trancount)不会增加。但是,如果使用TransactionScopeOption.RequiresNew,则尝试在新事务范围事务中注册连接时会收到一条有用的错误消息:“连接当前已注册事务。完成当前事务并重试。” 是的,如果您完成了加入该连接的事务,则可以安全地将连接加入一个新事务。 更新:如果您以前在连接上调用BeginTransaction,则当您尝试加入新的事务范围事务时,会引发一个略有不同的错误:“由于连接上正在进行本地事务,因此无法加入事务。请完成本地事务并重试。” 另一方面,您可以在SqlConnection进入事务范围事务中时安全地调用SqlConnection上的BeginTransaction,这实际上会将@@ trancount增加一个,这与使用嵌套事务范围的Required选项不同,这不会导致它增加。有趣的是,如果您随后使用Required选项继续创建另一个嵌套的交易范围,则不会收到错误消息,

Q9。是。无论参与C#代码中的活动事务范围是什么,命令都将参与连接所参与的任何事务。


11
在写完第8题的答案后,我意识到这些东西看起来开始变得像Magic:The Gathering!的规则一样复杂。除非情况更糟,否则因为TransactionScope文档没有解释任何这些。
Triynko 2010年

对于Q3,您是否使用相同的连接字符串同时打开两个连接?如果是这样,那将是一个分布式事务(即使使用SQL Server 2008)
Randy支持Monica 2010年

2
不。我编辑帖子以澄清。我的理解是,无论SQL Server版本如何,同时打开两个连接总是会导致分布式事务。在SQL 2008之前,使用相同的连接字符串一次仅打开一个连接仍会导致DT,但在SQL 2008中,使用相同的连接字符串一次打开一个连接(永远不会一次打开两个连接)不会导致DT
Triynko 2010年

1
为了澄清您对Q2的回答,如果两个命令在同一线程上顺序执行,则应可以正常运行。
贾里德·摩尔

2
关于SQL 2008中相同连接字符串的Q3升级问题,以下是MSDN引用:msdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx

19

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。只能通过使用自动征募来避免这种晋升。

(2)在.Net 4.0之前,我强烈建议在连接字符串中设置“ Transaction Binding = Explicit Unbind”。该问题已在.Net 4.0中修复,因此完全不需要“显式解除绑定”。

(3)自己滚动CommittableTransaction并设置Transaction.Current为该设置与设置基本上相同TransactionScope。实际上,这很少有用,仅是仅供参考。

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


我刚刚测试了这种情况,它似乎按照您的描述工作。此外,即使您使用自动登记,如果在打开第二个连接之前调用“ SqlConnection.ClearAllPools()”,它也会升级为分布式事务。
Triynko 2011年

如果这是真的,那么交易中就只能有一个“真实”连接。 这样,打开,关闭和重新打开TransactionScope事务中列出的连接而无需升级为分布式事务的能力实际上是连接池造成的一种幻觉,通常会使已释放的连接保持打开状态,如果重新连接则返回相同的确切连接-打开自动加入。
Triynko 2011年

因此,您真正要说的是,如果您避开了自动征募过程,那么当您在事务范围事务(TST)中重新打开一个新连接时,而不是连接池中会抓住正确的连接(最初是一个在TST中加入),它相当合适地抢占了一个全新的连接,当手动加入时,它会导致TST升级。
Triynko 2011年

无论如何,这就是我在回答Q1时所暗示的含义,当我提到它已被征召,除非在连接字符串中指定了“ Enlist = false”,然后再讨论池如何找到合适的连接。
Triynko 2011年

就多线程而言,如果您访问我对第二季度的回答中的链接,您会看到,虽然Transaction.Current对于每个线程都是唯一的,但是您可以轻松地在一个线程中获取引用并将其传递给另一个线程;但是,从两个不同的线程访问TST会导致一个非常特定的错误“另一个会话正在使用事务上下文”。要对TST进行多线程处理,必须创建一个DependantTransaction,但此时它必须是分布式事务,因为您需要第二个独立连接才能实际运行同时命令和MSDTC来协调两者。
Triynko 2011年

1

我们已经看到了另一种奇怪的情况,那就是如果您构建一个EntityConnectionStringBuilder,它将与TransactionScope.Current(并且我们认为)参与该交易有关。我们已经在调试器中观察到了这一点,其中TransactionScope.Currentcurrent.TransactionInformation.internalTransaction表示enlistmentCount == 1在构造之前和enlistmentCount == 2之后。

为了避免这种情况,请在内部进行构造

using (new TransactionScope(TransactionScopeOption.Suppress))

可能不在您的操作范围之内(每次需要连接时,我们都会进行构建)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.