在什么情况下,SqlConnection会自动列入一个环境TransactionScope事务?
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?) }
scheme2:使用连接在其外部打开的事务范围内
//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?) }
自从问这个问题以来,我做了一些testing,发现大部分(如果不是全部)我自己的答案,因为没有人回答。 请让我知道,如果我错过了什么。
Q1。 是的,除非在连接string中指定了“enlist = false”。 连接池find一个可用的连接。 一个可用的连接是一个没有在事务中登记的或者在同一个事务中被登记的连接。
Q2。 第二个连接是一个独立的连接,它参与同一个事务。 我不清楚这两个连接上的命令是如何交互的,因为它们是针对同一个数据库运行的,但是我认为如果同时发出命令,就会发生错误:像“正在使用的事务上下文另一个会话“
Q3。 是的,它被升级到一个分布式事务,因此,即使使用相同的连接string,获得多个连接也会导致它成为一个分布式事务,这可以通过检查Transaction.Current.TransactionInformation上的非空GUID .DistributedIdentifier。 *更新:我读了SQL Server 2008中解决这个问题的地方,当两个连接使用相同的连接string时(只要这两个连接没有同时打开),就不使用MSDTC。 这样可以在一个事务中多次打开一个连接并closures它,这样可以尽可能晚地打开连接并尽快closures,从而更好地利用连接池。
Q4。 不可以。没有事务范围处于活动状态时打开的连接将不会自动列入新创build的事务范围。
Q5。 不需要。除非您在事务范围内打开连接,或者在范围内征用现有连接,否则基本上没有事务。 您的连接必须自动或手动列入事务范围,以便您的命令参与事务。
Q6。 是的,不参与事务的连接上的命令将被提交为已发布,即使代码碰巧已在回滚的事务作用域块中执行。 如果连接不在当前事务处理范围内,则它不参与事务处理,因此提交或回滚事务将不会影响未在事务范围中注册的连接上发出的命令……正如此人所发现的 。 除非您了解自动登记过程,否则这是非常困难的事情:只有在活动事务范围内打开连接时才会发生。
Q7。 是。 通过调用EnlistTransaction(Transaction.Current),可以在当前事务范围中明确征用现有的连接。 您也可以通过使用DependentTransaction在事务中的单独线程上build立连接,但是像以前一样,我不确定在同一个事务中涉及同一个数据库的两个连接可能如何相互作用…并且可能会发生错误,当然第二个入伍的连接会导致事务升级到分布式事务。
Q8。 可能会引发错误。 如果使用了TransactionScopeOption.Required,并且连接已经在事务范围事务中登记,那么就没有错误; 事实上,没有为范围创build新事务,并且事务计数(@@ trancount)不会增加。 但是,如果您使用TransactionScopeOption.RequiresNew,则尝试在新的事务作用域事务中登记连接时会收到一条有用的错误消息:“连接当前有事务登记,完成当前事务并重试。 是的,如果你完成了交易的连接,你可以安全地在新的交易中登记连接。 更新:如果您之前在连接上调用了BeginTransaction,则当您尝试登记新的事务作用域事务时,将引发稍微不同的错误:“无法登记事务,因为连接正在进行本地事务。重试。” 另一方面,您可以安全地在SqlConnection上调用BeginTransaction并将其join到事务范围事务中,并且实际上将@@ trancount增加1,这与使用嵌套事务作用域的Required选项不同,后者不会导致它增加。 有趣的是,如果使用Required选项继续创build另一个嵌套事务处理作用域,则不会因为已经具有活动事务处理作用域事务而没有任何更改(请记住,在事务处理时不会增加@@ trancount范围事务已经处于活动状态,并使用“必需”选项)。
Q9。 是。 无论C#代码中的活动事务作用域是什么,命令都会参与连接join的任何事务。
好的工作Triynko,你的答案都看起来相当准确,完整的给我。 我想指出的其他一些事情:
(1)手动入伍
在你上面的代码中,你(正确地)显示这样的手动登记:
using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (TransactionScope ts = new TransactionScope()) { conn.EnlistTransaction(Transaction.Current); } }
但是,也可以这样做,在连接string中使用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之前,我强烈build议在连接string中设置“Transaction Binding = Explicit Unbind” 。 这个问题已经在.Net 4.0中解决了,显式的解除绑定完全没有必要。
(3)滚动您自己的CommittableTransaction
并将Transaction.Current
设置为与TransactionScope
所做的基本相同的事情。 这只是非常实用的,仅供参考。
(4) Transaction.Current
是线程静态的。 这意味着Transaction.Current
只在创buildTransactionScope
的线程上设置。 所以执行同一个TransactionScope
多个线程(可能使用Task
)是不可能的。
我们已经看到的另外一个奇怪的情况是,如果你构造一个EntityConnectionStringBuilder
它将会使用TransactionScope.Current
和(我们认为)在事务中登记。 我们在debugging器中观察到了这一点,其中TransactionScope.Current
的current.TransactionInformation.internalTransaction
在构造之前显示enlistmentCount == 1
,之后是enlistmentCount == 2
。
为了避免这个,在里面构build它
using (new TransactionScope(TransactionScopeOption.Suppress))
并可能超出您的操作范围(我们每次需要连接时都在构build它)。