SqlConnectionは、どのような状況で自動的にアンビエントTransactionScopeトランザクションに参加しますか?


201

SqlConnectionがトランザクションに "参加"するとはどういう意味ですか?接続で実行するコマンドがトランザクションに参加するという意味ですか?

その場合、どのような状況でSqlConnectionがアンビエントTransactionScope Transactionに自動的に登録されますか?

コードのコメントで質問を参照してください。各質問の答えに対する私の推測は、括弧内の各質問の後に続きます。

シナリオ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?)
}

回答:


188

この質問をして以来、私はいくつかのテストを行っており、誰も答えなかったので、自分ですべてではないにしてもほとんどの答えを見つけました。見逃してしまった場合はお知らせください。

Q1。はい、「enlist = false」が接続文字列で指定されていない限り。接続プールは、使用可能な接続を見つけます。使用可能な接続とは、トランザクションに参加していない接続、または同じトランザクションに参加している接続です。

Q2。2番目の接続は独立した接続であり、同じトランザクションに参加します。彼らは同じデータベースに対して実行しているので、私は、これら2つの接続上のコマンドの相互作用についてはよく分からないが、私はコマンドが同時に両方で発行されている場合、エラーが発生することができると思う:のようなエラー使用中のトランザクションコンテキスト」により、別のセッション」

Q3。はい、分散トランザクションにエスカレートされるため、同じ接続文字列であっても複数の接続を参加させると、分散トランザクションになります。これは、Transaction.Current.TransactionInformationでnull以外のGUIDを確認することで確認できます。 .DistributedIdentifier。*更新:これはSQL Server 2008で修正されているため、両方の接続で同じ接続文字列が使用されている場合(両方の接続が同時に開かれていない限り)はMSDTCは使用されません。これにより、トランザクション内で接続を複数回開いて閉じることができます。接続をできるだけ遅く開いて、できるだけ早く閉じることにより、接続プールをより有効に活用できます。

Q4。いいえ。アクティブなトランザクションスコープがないときに開かれた接続は、新しく作成されたトランザクションスコープに自動的に参加しません。

Q5。いいえ。トランザクションスコープで接続を開くか、既存の接続をスコープに登録しない限り、基本的にトランザクションはありません。コマンドをトランザクションに参加させるには、接続をトランザクションスコープに自動または手動で登録する必要があります。

Q6。はい、トランザクションに参加していない接続のコマンドは、コードがロールバックされたトランザクションスコープブロックで実行された場合でも、発行されたとおりにコミットされます。接続は現在のトランザクションスコープに参加されていない場合は、トランザクションスコープに参加していない接続で発行されるコマンドには影響を与えません...と、トランザクションをコミットまたはロールバックして、トランザクションに参加していないこの男が出ました。自動登録プロセスを理解しない限り、これを見つけるのは非常に困難です。これは、アクティブなトランザクションスコープ内で接続が開かたときにのみ発生します。

Q7。はい。EnlistTransaction(Transaction.Current)を呼び出すことで、既存の接続を現在のトランザクションスコープに明示的に登録できます。DependentTransactionを使用して、トランザクションの別のスレッドに接続を参加させることもできますが、以前と同じように、同じデータベースに対する同じトランザクションに関係する2つの接続がどのように相互作用し、エラーが発生するかわかりません。もちろん、2番目に参加する接続により、トランザクションは分散トランザクションにエスカレートします。

Q8。エラーがスローされる場合があります。TransactionScopeOption.Requiredが使用され、接続がすでにトランザクションスコープトランザクションに参加している場合、エラーは発生しません。実際、スコープ用に作成された新しいトランザクションはなく、トランザクション数(@@ trancount)は増加しません。ただし、TransactionScopeOption.RequiresNewを使用する場合、新しいトランザクションスコープトランザクションに接続を登録しようとすると、「接続には現在トランザクションが登録されています。現在のトランザクションを終了して再試行してください。」という有用なエラーメッセージが表示されます。そして、はい、接続が参加しているトランザクションを完了すると、新しいトランザクションに接続を安全に参加できます。 更新:以前に接続でBeginTransactionを呼び出した場合、新しいトランザクションスコープトランザクションに参加しようとすると、わずかに異なるエラーがスローされます。「ローカルトランザクションが接続で進行中のため、トランザクションに参加できません。ローカルトランザクションを終了して、リトライ。" 一方、トランザクションスコープトランザクションに参加している間にSqlConnectionでBeginTransactionを安全に呼び出すことができます。これにより、ネストされたトランザクションスコープのRequiredオプションを使用した場合とは異なり、@@ trancountが1増加します。増加する。興味深いことに、次にRequiredオプションを使用して別のネストされたトランザクションスコープを作成しても、エラーは発生しません。

Q9。はい。コマンドは、C#コード内のアクティブなトランザクションスコープに関係なく、接続が参加しているトランザクションに参加します。


11
Q8への回答を書き込んだ後、このことはMagic:The Gatheringのルールと同じくらい複雑に見え始めていることに気づきました。ただし、TransactionScopeのドキュメントではこれについて説明されていないため、これはさらに悪いことです。
Triynko、2010年

Q3の場合、同じ接続文字列を使用して2つの接続を同時に開きますか?その場合、それは分散トランザクションになります(SQL Server 2008を使用した場合でも)
ランディはモニカを

2
いいえ。明確にするために投稿を編集しています。SQL Serverのバージョンに関係なく、2つの接続を同時に開いていると、常に分散トランザクションが発生することを理解しています。SQL 2008より前は、同じ接続文字列で一度に1つの接続のみを開くとDTが発生しますが、SQL 2008では、同じ接続文字列で一度に1つの接続を開く(一度に2つ開くことはありません)と、 DT
Triynko、2010年

1
Q2に対する回答を明確にするために、2つのコマンドが同じスレッドで連続して実行された場合、2つのコマンドは正常に実行されるはずです。
Jared Moore

2
:SQL 2008で同じ接続文字列のための第3四半期のプロモーションの問題では、ここではMSDNの引用であるmsdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx
pseudocoder

19

すばらしい仕事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);
    }
}

ここでもう1つ注意する点があります。conn2が開かれると、接続プールのコードは、conn1と同じトランザクションに後でそれを登録することを認識しません。つまり、conn2にはconn1とは異なる内部接続が与えられます。次に、conn2が登録されると、2つの接続が登録されるため、トランザクションをMSDTCに昇格させる必要があります。この昇格は、自動登録を使用することによってのみ回避できます。

(2) .Net 4.0以前は、接続文字列に "Transaction Binding = Explicit Unbind"を設定することを強くお勧めします。この問題は.Net 4.0で修正され、明示的なバインド解除が完全に不要になりました。

(3)自分CommittableTransactionでローリングし、それに設定Transaction.Currentすることは、基本的に同じことTransactionScopeです。これが実際に役立つことはめったになく、FYIだけです。

(4) Transaction.Currentはスレッドスタティックです。これは、Transaction.Currentを作成したスレッドでのみ設定されることを意味しTransactionScopeます。そのため、複数のスレッドがTransactionScope(場合によってはを使用してTask)同じスレッドを実行することはできません。


私はこのシナリオをテストしましたが、あなたが説明しているように機能するようです。さらに、自動登録を使用する場合でも、2番目の接続を開く前に "SqlConnection.ClearAllPools()"を呼び出すと、分散トランザクションにエスカレートされます。
Triynko、2009

これが当てはまる場合、トランザクションに関与するのは単一の「実際の」接続のみです。 分散トランザクションにエスカレートせずにTransactionScopeトランザクションに登録されている接続を開いたり、閉じたり、再度開いたりする機能は、実際には接続プールによって作成された幻想であり、通常は破棄された接続を開いたままにし、再接続した場合とまったく同じ接続を返します-自動登録のために開かれました。
Triynko、2009

つまり、実際に言っているのは、自動登録プロセスを回避すると、正しい接続(最初に接続された接続)を取得する接続プールではなく、トランザクションスコープトランザクション(TST)内で新しい接続を再度開くときです。 TSTに登録されている場合)、完全に新しい接続を適切に取得します。手動で登録すると、TSTがエスカレートします。
Triynko、2009

とにかく、接続文字列で "Enlist = false"が指定されていない限り参加することを述べた後、プールが適切な接続を見つける方法について話し、Q1に対する私の回答でそれをほのめかしていました。
Triynko、2009

マルチスレッドに関して言えば、Q2に対する私の回答のリンクにアクセスすると、Transaction.Currentが各スレッドに固有である一方で、1つのスレッドで参照を簡単に取得して別のスレッドに渡すことができます。ただし、2つの異なるスレッドからTSTにアクセスすると、「トランザクションコンテキストが別のセッションで使用されています」という非常に具体的なエラーが発生します。TSTをマルチスレッド化するには、DependantTransactionを作成する必要がありますが、その時点で分散トランザクションである必要があります。これは、実際に同時コマンドを実行する2番目の独立した接続と2つを調整するMSDTCが必要なためです。
Triynko、2011

1

私たちが目にした他の奇妙な状況の1つは、構築した場合、EntityConnectionStringBuilderそれが不正にTransactionScope.Currentなり、(私たちが思うに)トランザクションに参加することです。デバッガーでこれを確認しました。ここで、TransactionScope.Current's current.TransactionInformation.internalTransactionは作成enlistmentCount == 1前と作成enlistmentCount == 2後を示しています。

これを回避するには、内部で構築します

using (new TransactionScope(TransactionScopeOption.Suppress))

おそらく操作の範囲外です(接続が必要になるたびに作成していました)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.