207

在事务中“登记” 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?)
}
4

3 回答 3

191

自从提出这个问题以来,我已经做了一些测试,并且我自己找到了大多数答案,因为没有其他人回答。如果我错过了什么,请告诉我。

Q1:连接会自动加入事务吗?

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

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

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

Q3:这第二个连接在当前事务范围内的自动登记是否会导致事务升级为分布式事务?

是的,它被升级为分布式事务,因此征用多个连接,即使使用相同的连接字符串,也会导致它成为分布式事务,这可以通过在Transaction.Current.TransactionInformation.DistributedIdentifier.

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

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

不会。当没有事务范围处于活动状态时打开的连接不会自动加入新创建的事务范围。

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

没有。除非您在事务范围内打开连接,或者在范围内征用现有连接,否则基本上没有事务。您的连接必须自动或手动加入事务范围,以便您的命令参与事务。

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

是的,不参与事务的连接上的命令在发出时提交,即使代码恰好在回滚的事务范围块中执行。如果连接未在当前事务范围内登记,则它不参与事务,因此提交或回滚事务将不会影响在未登记在事务范围内的连接上发出的命令......正如这个人发现的那样. 除非您了解自动登记过程,否则很难发现这一点:仅当在活动事务范围内打开连接时才会发生这种情况。

Q7:上述方法是否在当前环境事务中显式登记了预先存在的连接,以便我在连接上执行的命令现在参与环境事务?

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

Q8:如果我调用上述方法时,已有的连接已经加入到事务中,会发生什么?可能会抛出错误吗?

可能会抛出错误。如果TransactionScopeOption.Required使用了,并且连接已经在事务范围事务中登记,则没有错误;实际上,没有为范围创建新事务,事务计数 ( @@trancount) 并没有增加。但是,如果您使用TransactionScopeOption.RequiresNew,则在尝试在新的事务范围事务中登记连接时会收到一条有用的错误消息:“连接当前已登记事务。完成当前事务并重试。” 是的,如果您完成了登记连接的事务,您可以安全地在新事务中登记连接。

*更新:如果您之前BeginTransaction在连接上调用过,当您尝试加入新的事务范围事务时,会抛出一个稍微不同的错误:“无法加入事务,因为连接上正在进行本地事务。完成本地事务并重试。” 另一方面,您可以安全地调用它在事务范围事务中登记BeginTransactionSqlConnectionwhile,这实际上会增加@@trancount一个,这与使用嵌套事务范围的 Required 选项不同,它不会导致它增加。有趣的是,如果您随后使用该选项继续创建另一个嵌套事务范围Required,您将不会收到错误消息,因为已经有一个活动的事务范围事务没有任何变化(请记住@@trancount当事务范围事务已经处于活动状态并且使用了该Required选项时,不会增加)。

Q9:如果现有连接已经在事务中登记,并且我没有调用上述方法来登记它,我对其执行的任何命令是否会参与其现有事务而不是当前事务范围?

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

于 2010-05-21T23:57:52.990 回答
22

干得好 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)是不可能的。

于 2011-09-28T16:46:18.280 回答
1

我们看到的另一种奇怪的情况是,如果你构造一个EntityConnectionStringBuilder它会弄脏TransactionScope.Current并(我们认为)参与交易。我们已经在调试器中观察到了这一点,其中TransactionScope.Current'在构造之前和之后current.TransactionInformation.internalTransaction显示。enlistmentCount == 1enlistmentCount == 2

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

using (new TransactionScope(TransactionScopeOption.Suppress))

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

于 2017-07-06T22:36:14.663 回答