-
Notifications
You must be signed in to change notification settings - Fork 0
MS_TransactionScope
- 戻る(MS-DTC、データアクセスのいろいろ)
- TransactionScope
-
TransactionScopeクラスは、2 相コミットを実現するための .NET 2.0 の API であり、
内部的には、トランザクション マネージャ(TM)であるMS-DTCを使用している。 -
MS-DTCだけでなく、WS-Transaction(WCF)などで使われる予定だったが、
最近、これらの技術適用は非常に少なくなった。
補足(昇格:Lightweight Transaction Manager):
TransactionScopeは
常に MS-DTC を使うわけではない。
System.Transactionsの LTM(Lightweight Transaction Manager)が
関与するリソースの数に応じて段階的に昇格する。
段階 状況 コスト ローカル トランザクション 単一の接続のみ 低(通常の BEGIN TRANと同等)分散トランザクション 2 つ目のリソースが参加した時点で MS-DTC へ昇格 高(2 相コミット、in-doubt リスク) 注意すべきは、同じ DB への接続でも接続を 2 つ開けば昇格しうる点。
SQL Server 2008 以降は同一 DB への複数接続なら
昇格を回避できるケースがあるが、環境差が大きい。
「意図せず MS-DTC が有効化を要求されて落ちる」というのは典型的な事故で、
接続は 1 つに束ねるのが基本的な回避策である。
補足(最新化:.NET Core 以降の制約):
ランタイム MS-DTC への昇格 .NET Framework サポート .NET Core 〜 .NET 6 未サポート(昇格時に PlatformNotSupportedException).NET 7 以降 Windows のみサポート(Linux は未サポート) なお、
TransactionScopeそのもの(ローカル トランザクション止まりの利用)は
.NET Core 以降でも動作する。問題になるのは昇格が発生する場合だけである。
-
ASP.NET 1.x では、
-
MS-DTCは Enterprise Services 上からの利用に限られたが、
Enterprise Services を使用した場合、
ビジネス プロセス・ビジネス コンポーネントなどのアプリケーション ブロックを
Enterprise Services 上に切り出す必要があった。 - また、Enterprise Services アプリケーション開発は難易度が高く、
あまり活用されなかった。
-
MS-DTCは Enterprise Services 上からの利用に限られたが、
-
このため、ASP.NET 2.0 からは、
2 相コミットの動作確認用サンプル プログラムを用いて、
TransactionScope クラスの利用方法を説明する。
下記は、SQL Server 用のデータ アクセス ライブラリである、
SqlClient を利用した場合のサンプル コード。
private void Two_Phase_Commit(bool flag)
{
TransactionOptions txopt = new TransactionOptions();
txopt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.Snapshot;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, txopt))
{
using (SqlConnection con = new SqlConnection(@"Data Source=AAA;Initial Catalog=northwind;User ID=xxx;Password=xxx;"))
{
using (SqlCommand com = new SqlCommand())
{
com.Connection = con;
com.CommandText = "insert into table1(bbb) values('データ')";
try
{
con.Open();
com.ExecuteNonQuery();
}
finally
{
con.Close();
}
}
}
using (SqlConnection con = new SqlConnection(@"Data Source=BBB;Initial Catalog=northwind;User ID=xxx;Password=xxx;"))
{
using (SqlCommand com = new SqlCommand())
{
com.Connection = con;
com.CommandText = "insert into table1(bbb) values('データ')";
try
{
con.Open();
com.ExecuteNonQuery();
}
finally
{
con.Close();
}
}
}
if (flag) // flag変数はコミット、ロールバックのテストのための実装
{
scope.Complete();
}
else{ }
}
}- 分離レベルの選択が可能。Snapshot 分離レベルは、SQL Server 2005 以降でサポートされる。
-
using (TransactionScope scopeの Using ステートメント範囲内の
TransactionScopeオブジェクトの破棄までがトランザクション範囲となる
(Using ステートメント コードブロックは、範囲外に出る際に、
自動的にオブジェクトのDispose()メソッドを呼び出す仕様になっている)。 - このため、Using ステートメントを使用しない実装をする際は
手動でTransactionScope.Dispose()メソッドを呼び出す必要がある。 - 最後に、
TransactionScope.Complete()メソッドを呼び出し、
上記のプログラム中の 2 つの接続が持つトランザクションは
分散トランザクションとして 2 相コミットされる。 -
TransactionScope.Complete()メソッドを呼び出さない状態で
TransactionScopeオブジェクトが破棄された(Using ステートメントは、範囲外に出た)
場合は、分散トランザクションは、ロールバックされる。
補足(最新化:現在書くならこう): 上記のサンプルは .NET 2.0 当時のもので、
現在の実装では以下の 3 点を必ず押さえる。
TransactionScopeAsyncFlowOption.Enabledを指定する
これを付けないとawaitを挟んだ時点でアンビエント トランザクションが
引き継がれず、トランザクション外で実行されるという深刻なバグになる。- 既定の分離レベルは
Serializable
TransactionOptionsを省略するとSerializableになり、
ロック競合が激増する。上のサンプルのように明示指定すること。- SQL は必ずパラメータ化する
元のサンプルはリテラル埋め込みだが、実務ではパラメータを使う。var txopt = new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(30), }; using (var scope = new TransactionScope( TransactionScopeOption.Required, txopt, TransactionScopeAsyncFlowOption.Enabled)) { await using (var cn = new SqlConnection(cs)) { await cn.OpenAsync(); await using var cmd = new SqlCommand( "insert into table1(bbb) values(@v)", cn); cmd.Parameters.Add("@v", SqlDbType.NVarChar, 100).Value = "データ"; await cmd.ExecuteNonQueryAsync(); } scope.Complete(); }
補足(
TransactionScopeOptionの意味):
値 挙動 Required既存のアンビエント トランザクションに参加。無ければ新規作成(既定) RequiresNew常に新しいトランザクションを開始(外側とは独立してコミット/ロールバック) Suppressトランザクションの外で実行する(監査ログの記録など、ロールバックさせたくない処理に使う)
- 下記は、Oracle 用のデータ アクセス ライブラリである、
System.Data.OracleClient、ODP.NET を利用した場合のサンプル コードである。 - 実装を見てわかるとおり、ADO.NET の仕様に合わせ、
API のインターフェイスも共通化されているため、
他の DBMS のデータプロバイダも含め、実装に大きな違いは無い。
private void Two_Phase_Commit(bool flag)
{
TransactionOptions txopt = new TransactionOptions();
txopt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.Snapshot;
//txopt.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, txopt))
{
using (OracleConnection con = new OracleConnection(@"User Id=xxx;Password=xxx;Data Source=AAA/orcl;"))
{
using (OracleCommand com = new OracleCommand())
{
com.Connection = con;
com.CommandText = "insert into table1(aaa, bbb) values(1, 'データ')";
try
{
con.Open();
com.ExecuteNonQuery();
}
finally
{
con.Close();
}
}
}
using (OracleConnection con = new OracleConnection(@"User Id=xxx;Password=xxx;Data Source=BBB/orcl;"))
{
using (OracleCommand com = new OracleCommand())
{
com.Connection = con;
com.CommandText = "insert into table1(aaa, bbb) values(1, 'データ')";
try
{
con.Open();
com.ExecuteNonQuery();
}
finally
{
con.Close();
}
}
}
if (flag) // flag変数はコミット、ロールバックのテストのための実装
{
scope.Complete();
}
else{ }
}
}移行メモ(元コードの体裁): 元ページの Oracle 側サンプルは
finallyブロックの括弧の対応が崩れていたため、
SQL Server 側のサンプルに合わせて整形した(処理内容は変えていない)。
補足(分散トランザクションを避ける設計): 2 相コミットは
in-doubt トランザクションによる長時間ロックのリスクを伴うため、
現在は以下のいずれかで回避するのが定石。
- 1 つの DB に集約して単一のローカル トランザクションで済ませる
- Outbox パターン: 業務データの更新とメッセージの記録を
同一 DB・同一トランザクションで確定し、別プロセスが配送する- Saga パターン: 各サービスがローカルにコミットし、
失敗時は補償トランザクションで打ち消す詳細はクラウド設計パターンを参照。
Tags: 移行, インフラストラクチャ, Windows, データアクセス, ADO.NET
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。