トランザクション管理はデータベースの基本機能ですので,当然実装されていることは言うまでもありません。しかしその振る舞い方にはデータベース毎の特徴があり,注意が必要です。
BEGIN TRANSACTION
SQL ServerにはBEGIN TRANSACTION文がありますが,PL/SQLには該当するものがありません。これは,両者の違いを端的に表しています。つまり,SQL Serverにおいては,トランザクションで囲んでいない部分のSQL(DML)は,即時コミットされるのに対して,Oracleは常にトランザクションモードで動作していると言うことです。ディフォルトの動作が異なっているということですね。これは,Oracle特有の読み取り一貫性の副次的恩恵とも言えます。ただ,この結果,SQL ServerとOracleではトランザクションの扱い方が異なっています。なお,SQL Serverでもデータベースオプションの設定によりOracleと同等の読み取り一貫性を確保できるようになっています。SQL Serverでは,行のバージョニングと呼んでいます。
因みに,この両者の違いをよく示しているのは,SQLPLUSとSQL Server Management Studioにおける動作の違いです。SQLPLUSでコミットせずにSQLPLUSを終了した場合,DMLはコミットされます。一方,SQL Server Management StudioでBEGIN TRANSACTIONを実行後に行ったDMLをコミットせずに終了した場合,ロールバックされます。SQL Server Management Studioでは,そのために,コミット or ロールバックの確認ダイアログが表示されるようになっています。
BEGIN TRANSACTIONのネスト
SQL ServerにはBEGIN TRANSACTION文があるため,これが連続して実行された場合,コミット動作はどうなるのか,という問題が発生します。BEGIN TRANSACTIONが存在しないOracleでは,発生し得ない問題です。答えは,一番外側のBEGIN TRANSACTIONに対応するコミットがトランザクションをコミットすることになります。以下のようなSQLがあったとします。
<title>SQL Serverのトランザクション</title> <style type="text/css"> </style>
トランザクションをネストするなどというバグはあり得ない,と思いますか。ところが,そうはいかないのです。このトランザクションのネストは,ストアドプロシージャ越しや,ADO.Netとストアドプロシージャの間でも発生します。ですので,以下のようなことが起こりえます。
(1)T-SQLのストアドuspXxxA, uspXxxBをそれそれの内部でトランザクション管理をする処理として作成。
(2)その後,この2つのストアドを連続して実行するプログラムを.Netで作成。この際SqlConnectionとSqlTransactionでトランザクション管理を実施
これで,ストアドで定義していたトランザクション管理は無効になってしまいます。ですので,T-SQLストアドをADO.Net経由で使用する場合には注意が必要です。
ADO.Netのトランザクション管理
ここまでで,T-SQLのBEGIN TRANSACTIONはネストしてもエラーとはならないと述べましたが,ADO.Net上のトランザクション管理はネストできません。つまり,以下のようなコードを記述するとエラーが発生します。 <title>ADO.Net</title> <style type="text/css"> </style>
ADO.Net経由でのOracle接続とトランザクション管理
Oracle Data Provider for .NETもSystem.Data.Common.DbTransactionから派生しています。ですので,SQL ServerをADO.Net経由で操作するのと同じことが発生します。OCIダイレクト接続の場合には,不必要だったBEGIN TRANSACTIONが,ADO.Netを使用した場合には必要になるということです。
BEGIN TRANSACTION
SQL ServerにはBEGIN TRANSACTION文がありますが,PL/SQLには該当するものがありません。これは,両者の違いを端的に表しています。つまり,SQL Serverにおいては,トランザクションで囲んでいない部分のSQL(DML)は,即時コミットされるのに対して,Oracleは常にトランザクションモードで動作していると言うことです。ディフォルトの動作が異なっているということですね。これは,Oracle特有の読み取り一貫性の副次的恩恵とも言えます。ただ,この結果,SQL ServerとOracleではトランザクションの扱い方が異なっています。なお,SQL Serverでもデータベースオプションの設定によりOracleと同等の読み取り一貫性を確保できるようになっています。SQL Serverでは,行のバージョニングと呼んでいます。
因みに,この両者の違いをよく示しているのは,SQLPLUSとSQL Server Management Studioにおける動作の違いです。SQLPLUSでコミットせずにSQLPLUSを終了した場合,DMLはコミットされます。一方,SQL Server Management StudioでBEGIN TRANSACTIONを実行後に行ったDMLをコミットせずに終了した場合,ロールバックされます。SQL Server Management Studioでは,そのために,コミット or ロールバックの確認ダイアログが表示されるようになっています。
BEGIN TRANSACTIONのネスト
SQL ServerにはBEGIN TRANSACTION文があるため,これが連続して実行された場合,コミット動作はどうなるのか,という問題が発生します。BEGIN TRANSACTIONが存在しないOracleでは,発生し得ない問題です。答えは,一番外側のBEGIN TRANSACTIONに対応するコミットがトランザクションをコミットすることになります。以下のようなSQLがあったとします。
<title>SQL Serverのトランザクション</title> <style type="text/css"> </style>
0001 BEGIN TRANSACTION上の例では,1行目と4行目にBEGIN TRANSACTIONがあります。それぞれに対応するCOMMITは7行目と9行目です。この場合,コミットが有効になるのは,9行目のコミットを実行した時点となります。またもし,9行目がROLLBACK TRANSACTIONであったとすれば,TableA,TableB双方のUPDATEがロールバックされます。
0002 UPDATE TableA ...
0003
0004 BEGIN TRANSACTION
0005 UPDATE TableB ...
0006
0007 COMMIT;
0008
0009 COMMIT;
トランザクションをネストするなどというバグはあり得ない,と思いますか。ところが,そうはいかないのです。このトランザクションのネストは,ストアドプロシージャ越しや,ADO.Netとストアドプロシージャの間でも発生します。ですので,以下のようなことが起こりえます。
(1)T-SQLのストアドuspXxxA, uspXxxBをそれそれの内部でトランザクション管理をする処理として作成。
(2)その後,この2つのストアドを連続して実行するプログラムを.Netで作成。この際SqlConnectionとSqlTransactionでトランザクション管理を実施
これで,ストアドで定義していたトランザクション管理は無効になってしまいます。ですので,T-SQLストアドをADO.Net経由で使用する場合には注意が必要です。
ADO.Netのトランザクション管理
ここまでで,T-SQLのBEGIN TRANSACTIONはネストしてもエラーとはならないと述べましたが,ADO.Net上のトランザクション管理はネストできません。つまり,以下のようなコードを記述するとエラーが発生します。 <title>ADO.Net</title> <style type="text/css"> </style>
0001 Dim tranSSFirst As SqlTransaction同一DBに対してこのような2重トランザクションを行うことはあり得ませんが,try catch構文によるRollback処理の実装漏れがあると,結果として同様の状態になります。この場合,エラーが発生するのは,本来のバグであるRollback漏れのコード部分ではなく,次回のBeginTransaction実行時となります。そのため,エラーの発生原因の特定に手間取る場合が多いようです。
0002 Dim tranSSSecond As SqlTransaction
0003
0004 tranSSFirst = conSS.BeginTransaction
0005 tranSSSecond = conSS.BeginTransaction
0006
0007 Try
0008 ' DMLの実行
0009
0010 tranSSSecond.Commit()
0011 tranSSFirst.Commit()
0012 Catch ex As Exception
0013 MessageBox.Show(ex.Message.ToString())
0014 tranSSSecond.Rollback()
0015 tranSSFirst.Rollback()
0016 End Try
ADO.Net経由でのOracle接続とトランザクション管理
Oracle Data Provider for .NETもSystem.Data.Common.DbTransactionから派生しています。ですので,SQL ServerをADO.Net経由で操作するのと同じことが発生します。OCIダイレクト接続の場合には,不必要だったBEGIN TRANSACTIONが,ADO.Netを使用した場合には必要になるということです。