回答には少し遅れていますが、他の人にも役立つことを願っています。回答には3つの部分があります:
- 「別のセッションで使用されているトランザクションコンテキスト」とはどういう意味ですか。
- エラー「別のセッションで使用されているトランザクションコンテキスト」を再現する方法
1。 「別のセッションで使用されているトランザクションコンテキスト」とはどういう意味ですか。
重要なお知らせ:トランザクションコンテキストロックは、SqlConnection
間の相互作用の直前に取得され、直後に解放されます。 およびSQLServer。
SQLクエリを実行するときは、SqlConnection
「見た目」は、それをラップするトランザクションがあります。 SqlTransaction
の可能性があります (SqlConnectionの場合は「ネイティブ」)またはTransaction
System.Transactions
から 組み立て。
トランザクションが見つかったときSqlConnection
これを使用してSQLServerと通信し、現時点ではTransaction
と通信します。 コンテキストは排他的にロックされています。
TransactionScope
とは ? Transaction
を作成します .NET Frameworkコンポーネントに関する情報を提供するため、SqlConnectionを含むすべての人がそれを使用できます(設計上は使用する必要があります)。
したがって、TransactionScope
を宣言します 現在のThread
でインスタンス化されたすべての「トランザクション可能」オブジェクトで使用できる新しいトランザクションを作成しています 。
説明されているエラーとは、次のことを意味します。
- いくつかの
SqlConnections
を作成しました 同じTransactionContext
の下で (つまり、同じトランザクションに関連している) - これらの
SqlConnection
に質問しました SQLServerと同時に通信する - そのうちの1つが現在の
Transaction
をロックしました コンテキストと次の1つがエラーをスローしました
2。エラー「別のセッションで使用されているトランザクションコンテキスト」を再現する方法
まず、SQLコマンドの実行時にトランザクションコンテキストが使用(「ロック」)されます。したがって、そのような動作を確実に再現することは困難です。
ただし、単一のトランザクションで比較的長いSQL操作を実行する複数のスレッドを開始することでそれを試みることができます。テーブル[dbo].[Persons]
を準備しましょう。 [tests]
で データベース:
USE [tests]
GO
DROP TABLE [dbo].[Persons]
GO
CREATE TABLE [dbo].[Persons](
[Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [nvarchar](1024) NOT NULL,
[Nick] [nvarchar](1024) NOT NULL,
[Email] [nvarchar](1024) NOT NULL)
GO
DECLARE @Counter INT
SET @Counter = 500
WHILE (@Counter > 0) BEGIN
INSERT [dbo].[Persons] ([Name], [Nick], [Email])
VALUES ('Sheev Palpatine', 'DarthSidious', '[email protected]')
SET @Counter = @Counter - 1
END
GO
そして、「別のセッションで使用されているトランザクションコンテキスト」を再現します。 Shrikeコード例に基づくC#コードのエラー
using System;
using System.Collections.Generic;
using System.Threading;
using System.Transactions;
using System.Data.SqlClient;
namespace SO.SQL.Transactions
{
public static class TxContextInUseRepro
{
const int Iterations = 100;
const int ThreadCount = 10;
const int MaxThreadSleep = 50;
const string ConnectionString = "Initial Catalog=tests;Data Source=.;" +
"User ID=testUser;PWD=Qwerty12;";
static readonly Random Rnd = new Random();
public static void Main()
{
var txOptions = new TransactionOptions();
txOptions.IsolationLevel = IsolationLevel.ReadCommitted;
using (var ctx = new TransactionScope(
TransactionScopeOption.Required, txOptions))
{
var current = Transaction.Current;
DependentTransaction dtx = current.DependentClone(
DependentCloneOption.BlockCommitUntilComplete);
for (int i = 0; i < Iterations; i++)
{
// make the transaction distributed
using (SqlConnection con1 = new SqlConnection(ConnectionString))
using (SqlConnection con2 = new SqlConnection(ConnectionString))
{
con1.Open();
con2.Open();
}
var threads = new List<Thread>();
for (int j = 0; j < ThreadCount; j++)
{
Thread t1 = new Thread(o => WorkCallback(dtx));
threads.Add(t1);
t1.Start();
}
for (int j = 0; j < ThreadCount; j++)
threads[j].Join();
}
dtx.Complete();
ctx.Complete();
}
}
private static void WorkCallback(DependentTransaction dtx)
{
using (var txScope1 = new TransactionScope(dtx))
{
using (SqlConnection con2 = new SqlConnection(ConnectionString))
{
Thread.Sleep(Rnd.Next(MaxThreadSleep));
con2.Open();
using (var cmd = new SqlCommand("SELECT * FROM [dbo].[Persons]", con2))
using (cmd.ExecuteReader()) { } // simply recieve data
}
txScope1.Complete();
}
}
}
}
そして結論として、アプリケーションにトランザクションサポートを実装することについてのいくつかの言葉:
- 可能であれば、マルチスレッドデータ操作は避けてください(ロードまたは保存に関係なく)。例えば。
SELECT
を保存します /UPDATE
/ etc ...単一のキューでリクエストし、シングルスレッドワーカーでそれらを処理します; - マルチスレッドアプリケーションでは、トランザクションを使用します。いつも。どこにでも。読むためにも;
- 複数のスレッド間で単一のトランザクションを共有しないでください。それは奇妙で、自明ではなく、超越的で、再現性がない原因となります。 エラーメッセージ:
- 「別のセッションで使用されているトランザクションコンテキスト。」:1つのトランザクションでサーバーとの複数の同時インタラクション。
- 「タイムアウトが期限切れになりました。操作が完了する前にタイムアウト期間が経過したか、サーバーが応答していません。」:依存しないトランザクションが完了しました。
- 「取引は疑わしいです。」;
- ...そして私は他にもたくさんのことを想定しています...
-
TransactionScope
の分離レベルを設定することを忘れないでください 。デフォルトはSerializable
ただし、ほとんどの場合、ReadCommitted
十分です; - Complete()
TransactionScope
を忘れないでください およびDependentTransaction