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

Tri*_*nko 198 c# ado.net sqlcommand 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?)
}
Run Code Online (Sandbox Code Playgroud)

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

//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?)
}
Run Code Online (Sandbox Code Playgroud)

Tri*_*nko 183

自从提出这个问题后我做了一些测试,并且发现了大部分(如果不是全部)答案,因为没有其他人回复.如果我遗漏了什么,请告诉我.

Q1.是的,除非在连接字符串中指定了"enlist = false".连接池找到可用的连接.可用连接是指未在事务中登记的连接或在同一事务中登记的连接.

Q2.第二个连接是一个独立的连接,它参与同一个事务.我不确定命令在这两个连接上的交互,因为它们是针对同一个数据库运行的,但我认为如果同时在两个连接上发出命令,则会发生错误:如"使用的事务上下文"之类的错误另一场会议"

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

Q4.否.当没有事务范围处于活动状态时打开的连接将不会自动登记在新创建的事务范围中.

Q5.否.除非您在事务范围中打开连接,或者在范围内登记现有连接,否则基本上没有TRANSACTION.您的连接必须在事务范围中自动或手动登记,以便您的命令参与事务.

Q6.是的,未参与事务的连接上的命令将被提交为已发出,即使代码恰好已在已回滚的事务范围块中执行.如果连接未在当前事务范围中登记,则它不参与事务,因此提交或回滚事务将不会对在事务范围中未登记的连接上发出的命令产生影响...正如此人发现的那样.这是一个很艰巨察觉,除非你理解了自动征用过程:它仅在连接打开时里面一个活跃的事务范围.

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

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

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

  • 在写完Q8的答案之后,我意识到这些东西开始变得像Magic:The Gathering的规则一样复杂!除非情况更糟,因为TransactionScope文档没有解释任何此类问题. (11认同)
  • 不.我编辑帖子以澄清.我的理解是,无论SQL Server版本如何,同时打开两个连接总是会导致分布式事务.在SQL 2008之前,一次只打开一个连接,使用相同的连接字符串仍然会导致DT,但是对于SQL 2008,使用相同的连接字符串打开一个连接(从不打开两次)将不会导致DT (2认同)
  • 关于SQL 2008中相同连接字符串的Q3升级问题,这里是MSDN引用:http://msdn.microsoft.com/en-us/library/ms172070(v = vs.90).aspx (2认同)

Jar*_*ore 19

不错的工作Triynko,你的答案看起来都非常准确和完整.我想指出的其他一些事情:

(1)人工入伍

在上面的代码中,您(正确)显示了这样的手动登记:

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,也可以这样做,在连接字符串中使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

还有另一件事需要注意.当conn2打开时,连接池代码不知道您以后要在与conn1相同的事务中登记它,这意味着conn2被赋予与conn1不同的内部连接.然后,当conn2被登记时,现在有2个连接登记,因此必须将事务提升为MSDTC.只有使用自动登记才能避免此促销.

(2)在.Net 4.0之前,我强烈建议在连接字符串中设置"Transaction Binding = Explicit Unbind".此问题已在.Net 4.0中修复,使得Explicit Unbind完全没必要.

(3)滚动你自己CommittableTransaction并设置到那与Transaction.Current基本相同的东西TransactionScope.这仅仅是FYI,实际上很少有用.

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