1. ホーム
  2. c#

[解決済み] [Solved] SqlConnectionが自動的にアンビエントTransactionScopeトランザクションに参加するのは、どのような状況においてですか?

2022-05-03 11:22:30

質問

SqlConnection がトランザクションに "enlisted" されるのはどういう意味ですか? それは単に、私が接続上で実行するコマンドがトランザクションに参加することを意味するのでしょうか?

その場合、どのような状況下で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?)
}

解決方法は?

この質問をした後、いくつかのテストを行い、他の誰も回答しなかったので、すべてではないにしても、ほとんどの答えを自分で見つけました。 もし私が見逃していることがあれば教えてください。

<ブロッククオート

Q1: 接続は自動的にトランザクションに参加するのでしょうか?

はい。 enlist=false が接続文字列で指定されている場合。 コネクションプールは、使用可能な接続を見つける。 使用可能な接続とは、トランザクションに登録されていない接続、または同じトランザクションに登録されている接続のことです。

<ブロッククオート

Q2: 今、同じ接続文字列で、2つ目の接続を開いた(そしてコマンドを実行した)場合、この2つ目の接続と1つ目の接続の関係はどうなりますか?

2つ目の接続は独立した接続であり、同じトランザクションに参加するものです。 この2つの接続は同じデータベースに対して実行されているので、コマンドの相互作用についてはよくわかりませんが、同時に両方の接続でコマンドを発行すると、次のようなエラーが発生する可能性があると思います。 トランザクションコンテキストは別のセッションによって使用されています。

<ブロッククオート

Q3: この2つ目の接続が現在のトランザクションスコープに自動的に参加することで、トランザクションが分散型トランザクションにエスカレートされますか?

はい、分散型トランザクションにエスカレーションされます。したがって、同じ接続文字列であっても、複数の接続を登録すると分散型トランザクションになります。 Transaction.Current.TransactionInformation.DistributedIdentifier .

* Update: どこかで読んだのですが、SQL Server 2008ではこの問題は修正され、両方の接続に同じ接続文字列を使用する場合(両方の接続が同時に開いていない限り)、MSDTCは使用されないようになったそうです。 これにより、トランザクション内で何度も接続を開いて閉じることができるようになり、接続をできるだけ遅く開き、できるだけ早く閉じることで、接続プールをより有効に活用できるようになるかもしれません。

<ブロッククオート

Q4: 今、コネクション上でコマンドの実行を開始すると、自動的に現在のトランザクションスコープに参加するようになりますか?

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

<ブロッククオート

Q5: アンビエントトランザクションに参加しない場合、コネクション上で実行したコマンドはアンビエントトランザクションに参加することになりますか?

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

<ブロッククオート

Q6:この接続上のコマンドが現在のトランザクションに参加していない場合、現在のトランザクションスコープをロールバックしてもコミットされるのでしょうか?

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

Q7: 上記の方法は、既存の接続を現在のアンビエントトランザクションに明示的に参加させ、その接続上で私が実行するコマンドがアンビエントトランザクションに参加するようにしますか?

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

<ブロッククオート

Q8: 上記のメソッドを呼び出したときに、既存の接続がすでにトランザクションに参加していた場合、どうなりますか? エラーが投げられるかもしれませんか?

エラーが発生する場合があります。 もし TransactionScopeOption.Required が使用され、その接続がすでにトランザクションスコープのトランザクションに登録されていた場合、エラーは発生しません。実際、そのスコープに新しいトランザクションは作成されず、トランザクションカウント( @@trancount ) が増えることはありません。 しかし、もし TransactionScopeOption.RequiresNew この場合、新しいトランザクション・スコープのトランザクションに接続を参加させようとすると、役に立つエラーメッセージが表示されます。 そして、接続が参加しているトランザクションを完了すれば、その接続を新しいトランザクションに安全に参加させることができます。

更新: 以前に BeginTransaction 接続上でローカル トランザクションが進行中であるため、トランザクションに参加することができません。 ローカル・トランザクションを終了して再試行してください。 BeginTransaction の上で SqlConnection トランザクションスコープのトランザクションに参加することで、実際に @@trancount ネストされたトランザクションスコープのRequiredオプションを使用しても増加しないのとは異なり、1つずつ増加します。 興味深いことに、別のネストされたトランザクションスコープに Required というのも、すでにアクティブなトランザクションスコープのトランザクションがあるため、何も変わらないからである(Remember) @@trancount は、トランザクションスコープのトランザクションがすでにアクティブで、かつ Required オプションが使用されます)。

Q9: 既存の接続がすでにトランザクションに登録されていて、それを登録するために上記のメソッドを呼び出さなかった場合、私がそれに対して実行するコマンドは、現在のトランザクションスコープではなく、その既存のトランザクションに参加しますか?

はい、そうです。C#コードのアクティブなトランザクション・スコープが何であるかにかかわらず、接続が参加するすべてのトランザクションにコマンドを参加させます。