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: 同一の接続文字列を使用して 2 番目の接続を開く (そしてコマンドを実行する) 場合、この 2 番目の接続と最初の接続との関係はどのようなものですか?

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

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

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

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

Q4: 今すぐ接続でコマンドの実行を開始すると、現在のトランザクション スコープに自動的に登録されますか?

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

Q5: 参加していない場合、接続で実行するコマンドはアンビエント トランザクションに参加しますか?

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

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

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

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

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

Q8: 上記のメソッドを呼び出したときに、既存の接続が既にトランザクションに登録されていた場合、どうなりますか? エラーがスローされる可能性がありますか?

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

*更新: 以前BeginTransactionに接続で呼び出した場合、新しいトランザクション スコープ トランザクションに登録しようとすると、わずかに異なるエラーがスローされます。リトライ。" 一方、トランザクション スコープ トランザクションに参加しBeginTransactionている間は安全に呼び出すことができ、ネストされたトランザクション スコープの Required オプションを使用した場合とは異なり、SqlConnection実際には1 増加します。@@trancount興味深いことに、オプションを使用してネストされた別のトランザクション スコープを作成し続けた場合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);
    }
}

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

(2) .Net 4.0より前では、接続文字列に「Transaction Binding=ExplicitUnbind」を設定することを強くお勧めします。この問題は.Net4.0で修正されており、ExplicitUnbindは完全に不要です。

(3)自分でロールしてそれCommittableTransactionに設定Transaction.Currentすることは、基本的に同じことですTransactionScope。これが実際に役立つことはめったになく、参考までに。

(4) Transaction.Currentはスレッド静的です。これは、Transaction.Currentを作成したスレッドにのみ設定されることを意味しTransactionScopeます。したがって、同じTransactionScope(おそらくを使用してTask)を実行する複数のスレッドは不可能です。

于 2011-09-28T16:46:18.280 に答える