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

发布于 2024-09-02 06:49:23 字数 2642 浏览 8 评论 0原文

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

What does it mean for an SqlConnection to be "enlisted" in a transaction? Does it simply mean that commands I execute on the connection will participate in the transaction?

If so, under what circumstances is an SqlConnection automatically enlisted in an ambient TransactionScope Transaction?

See questions in code comments. My guess to each question's answer follows each question in parenthesis.

Scenario 1: Opening connections INSIDE a transaction scope

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

Scenario 2: Using connections INSIDE a transaction scope that were opened OUTSIDE of it

//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?)
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

记忆消瘦 2024-09-09 06:49:23

自从问这个问题以来,我做了一些测试,并找到了大多数(如果不是全部)答案,因为没有其他人回答。如果我错过了什么,请告诉我。

问题1:连接是否会自动登记在事务中?

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

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

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

问题3:第二个连接自动加入当前事务范围是否会导致事务升级为分布式事务?

是的,它会升级为分布式事务,因此即使使用相同的连接字符串,登记多个连接也会导致它成为分布式事务,这可以通过检查 Transaction 中的非空 GUID 来确认。 Current.TransactionInformation.DistributedIdentifier

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

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

不会。在没有活动事务范围时打开的连接不会自动登记在新创建的事务范围中。

问题5:如果未登记,我在连接上执行的命令现在会参与环境事务吗?

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

问题6:如果该连接上的命令不参与当前事务,那么即使回滚当前事务范围,这些命令也会提交吗?

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

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

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

问题8:如果当我调用上述方法时,现有连接已经登记在事务中,会发生什么?可能会抛出错误吗?

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

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

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

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

I've done some tests since asking this question and found most if not all answers on my own, since no one else replied. Please let me know if I've missed anything.

Q1: Is connection automatically enlisted in transaction?

Yes, unless enlist=false is specified in the connection string. The connection pool finds a usable connection. A usable connection is one that's not enlisted in a transaction or one that's enlisted in the same transaction.

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?

The second connection is an independent connection, which participates in the same transaction. I'm not sure about the interaction of commands on these two connections, since they're running against the same database, but I think errors can occur if commands are issued on both at the same time: errors like "Transaction context in use by another session"

Q3: Will this second connection's automatic enlistment in the current transaction scope cause the transaction to be escalated to a distributed transaction?

Yes, it gets escalated to a distributed transaction, so enlisting more than one connection, even with the same connection string, causes it to become a distributed transaction, which can be confirmed by checking for a non-null GUID at Transaction.Current.TransactionInformation.DistributedIdentifier.

*Update: I read somewhere that this is fixed in SQL Server 2008, so that MSDTC is not used when the same connection string is used for both connections (as long as both connections are not open at the same time). That allows you to open a connection and close it multiple times within a transaction, which could make better use of the connection pool by opening connections as late as possible and closing them as soon as possible.

Q4: If I start executing commands on the connection now, will it automatically become enlisted in the current transaction scope?

No. A connection opened when no transaction scope was active, will not be automatically enlisted in a newly created transaction scope.

Q5: If not enlisted, will commands I execute on the connection now participate in the ambient transaction?

No. Unless you open a connection in the transaction scope, or enlist an existing connection in the scope, there basically is NO TRANSACTION. Your connection must be automatically or manually enlisted in the transaction scope in order for your commands to participate in the transaction.

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, 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.

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. 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: If the existing connection was already enlisted in a transaction when I called the above method, what would happen? Might an error be thrown?

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: 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 its existing transaction rather than the current transaction scope?

Yes. Commands participate in whatever transaction the connection is enlisted in, regardless of what the active transaction scope is in the C# code.

生生不灭 2024-09-09 06:49:23

干得好,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之前,我强烈建议设置 连接字符串中的“事务绑定=显式解除绑定”。此问题在 .Net 4.0 中已得到修复,因此完全不需要显式解除绑定。

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

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

Nice work Triynko, your answers all look quite accurate and complete to me. Some other things I would like to point out:

(1) Manual enlistment

In your code above, you (correctly) show manual enlistment like this:

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}

However, it is also possible to do it like this, using Enlist=false in the connection string.

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);
    }
}

There is another thing to note here. When conn2 is opened, the connection pool code doesn't know that you want to later enlist it in the same transaction as conn1, which means that conn2 is given a different internal connection than conn1. Then when conn2 is enlisted, there are now 2 connections enlisted so the transaction must be promoted to MSDTC. This promotion can only be avoided by using automatic enlistment.

(2) Before .Net 4.0, I highly recommend setting "Transaction Binding=Explicit Unbind" in the connection string. This issue is fixed in .Net 4.0, making Explicit Unbind totally unnecessary.

(3) Rolling your own CommittableTransaction and setting Transaction.Current to that is essentially the same thing as what TransactionScope does. This is rarely actually useful, just FYI.

(4) Transaction.Current is thread-static. This means that Transaction.Current is only set on the thread that created the TransactionScope. So multiple threads executing the same TransactionScope (possibly using Task) is not possible.

記柔刀 2024-09-09 06:49:23

我们看到的另一种奇怪的情况是,如果您构造一个 EntityConnectionStringBuilder ,它将与 TransactionScope.Current 混在一起,并(我们认为)参与事务。我们在调试器中观察到了这一点,其中 TransactionScope.Currentcurrent.TransactionInformation.internalTransaction 在构造之前显示 enlistmentCount == 1,并且之后enlistmentCount == 2

为了避免这种情况,

请使用 (new TransactionScope(TransactionScopeOption.Suppress)) 在 内部构造它

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

One other bizarre situation we've seen is that if you construct an EntityConnectionStringBuilder it will muck with TransactionScope.Current and (we think) enlist in the transaction. We've observed this in the debugger, where TransactionScope.Current's current.TransactionInformation.internalTransaction shows enlistmentCount == 1 before constructing, and enlistmentCount == 2 afterward.

To avoid this, construct it inside

using (new TransactionScope(TransactionScopeOption.Suppress))

and possibly outside the scope of your operation (we were constructing it every time we needed a connection).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文