在事务中“征募”一个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?)
}

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

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

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

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

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

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

是的,它被升级为分布式事务,因此招募多个连接(即使使用相同的连接字符串)会导致它成为分布式事务,这可以通过在transaction . current . transactioninformation . distributedidentifier中检查非空GUID来确认。

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

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

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

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

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

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

Yes, commands on a connection not participating in a transaction are committed as issued, even though the code happens to have executed in a transaction scope block that got rolled back. If the connection is not enlisted in the current transaction scope, it's not participating in the transaction, so committing or rolling back the transaction will have no effect on commands issued on a connection not enlisted in the transaction scope... as this guy found out. That's a very hard one to spot unless you understand the automatic enlistment process: it occurs only when a connection is opened inside an active transaction scope.

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

Yes. An existing connection can be explicitly enlisted in the current transaction scope by calling EnlistTransaction(Transaction.Current). You can also enlist a connection on a separate thread in the transaction by using a DependentTransaction, but like before, I'm not sure how two connections involved in the same transaction against the same database may interact... and errors may occur, and of course the second enlisted connection causes the transaction to escalate to a distributed transaction.

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

An error may be thrown. If TransactionScopeOption.Required was used, and the connection was already enlisted in a transaction scope transaction, then there is no error; in fact, there's no new transaction created for the scope, and the transaction count (@@trancount) does not increase. If, however, you use TransactionScopeOption.RequiresNew, then you get a helpful error message upon attempting to enlist the connection in the new transaction scope transaction: "Connection currently has transaction enlisted. Finish current transaction and retry." And yes, if you complete the transaction the connection is enlisted in, you can safely enlist the connection in a new transaction.

*Update: If you previously called BeginTransaction on the connection, a slightly different error is thrown when you try to enlist in a new transaction scope transaction: "Cannot enlist in the transaction because a local transaction is in progress on the connection. Finish local transaction and retry." On the other hand, you can safely call BeginTransaction on the SqlConnection while its enlisted in a transaction scope transaction, and that will actually increase @@trancount by one, unlike using the Required option of a nested transaction scope, which does not cause it to increase. Interestingly, if you then go on to create another nested transaction scope with the Required option, you will not get an error, because nothing changes as a result of already having an active transaction scope transaction (remember @@trancount is not increased when a transaction scope transaction is already active and the Required option is used).

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。这种提升只能通过使用自动入伍来避免。

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

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

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


我们看到的另一个奇怪的情况是,如果你构造了一个EntityConnectionStringBuilder,它就会混淆TransactionScope。当前和(我们认为)登记在事务中。我们已经在调试器中观察到这一点,其中TransactionScope。Current的Current . transactioninformation . internaltransaction在构造之前显示enlistmentCount == 1,在构造之后显示enlistmentCount == 2。

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

使用(new TransactionScope(TransactionScopeOption.Suppress))

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