読み取りトランザクションをコミットまたはロールバックする必要がありますか?


95

分離レベルを指定できるように、トランザクション内で実行する読み取りクエリがあります。クエリが完了したら、どうすればよいですか?

  • トランザクションをコミットします
  • トランザクションをロールバックする
  • 何もしない(トランザクションがusingブロックの最後でロールバックされる原因になります)

それぞれを行うことの意味は何ですか?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

編集:問題は、トランザクションを使用する必要があるかどうか、またはトランザクションレベルを設定する他の方法があるかどうかではありません。問題は、何も変更しないトランザクションがコミットまたはロールバックされるという違いがあるかどうかです。パフォーマンスに違いはありますか?他の接続に影響しますか?他に違いはありますか?


1
おそらくこれについてはすでにご存じでしょうが、提供した例では、クエリを実行するだけで同等の結果が得られる可能性があります。SELECT * FROM SomeTable with NOLOCK
JasonTrue

@Stefan、私たちのほとんどは、なぜ読み取り専用操作でトランザクションを行うのが面倒なのかと思っているようです。NOLOCKについて知っているかどうか、知っている場合は、なぜそのルートに行かなかったかをお知らせください。
StingyJack 2008年

NOLOCKについては知っていますが、このシステムはSQL Serverだけでなくさまざまなデータベースに対しても機能するため、SQL Server固有のロックヒントを回避しようとしています。アプリケーションは上記のコードで正常に動作しているため、これは何よりも好奇心からの質問です。
Stefan Moser

ああ、その場合、MSSqlServerがターゲット製品であることを示すため、sqlserverタグを削除します。
StingyJack 2008年

@StingyJack-そうです、sqlserverタグを使用するべきではありませんでした。
Stefan Moser

回答:


51

あなたがコミットします。限目。他の賢明な代替手段はありません。トランザクションを開始した場合は、それを閉じる必要があります。コミットすると、ロックが解除される可能性があり、ReadUncommittedまたはSerializableの分離レベルでも同様に適切です。暗黙的なロールバックに依存することは、おそらく技術的には同等ですが、形式としては不十分です。

それでも納得できない場合は、コードの途中にupdateステートメントを挿入し、発生する暗黙のロールバックを追跡してデータを削除する必要がある次の人を想像してみてください。


44
ロールバック-賢明な代替手段があります。明示的なロールバック、つまり。何かを変更するつもりがなかった場合は、ロールバックにより、すべてが取り消されます。もちろん、変更はありませんでした。ロールバックはそれを保証します。
Jonathan Leffler、

2
DBMSによって、「暗黙のトランザクション完了」セマンティクスが異なる場合があります。IBM Informix(および私はDB2)は暗黙的なロールバックを行います。噂によると、Oracleは暗黙のコミットを行います。私は暗黙的なロールバックを好みます。
ジョナサンレフラー

8
一時テーブルを作成し、IDを入力し、データテーブルと結合して、IDに関連するデータを選択し、一時テーブルを削除するとします。私は本当にデータを読んでいるだけで、一時テーブルなので一時テーブルがどうなるか気にしません...しかし、パフォーマンスの観点からは、トランザクションをロールバックまたはコミットする方がコストがかかりますか?一時テーブルと読み取り操作しか含まれていない場合のコミット/ロールバックの効果は何ですか?
Triynko、2010年

4
@Triynko-直感的には、ROLLBACKの方が高価だと思います。COMMITは通常の使用例であり、ROLLBACKは例外的な例です。しかし、学問を除いて、誰が気にしますか?あなたのアプリには1000の最適化ポイントがあると確信しています。あなたが本当に好奇心旺盛であれば、あなたはでコードを扱うMySQLのトランザクションを見つけることができますbazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/...
マーク・ブラケット・

3
@Triynko- 最適化する唯一の方法は、プロファイルを作成することです。これは単純なコード変更であり、本当に最適化したい場合に両方のメソッドをプロファイリングしない理由はありません。必ず結果を更新してください!
Mark Brackett 2010年

28

何も変更していない場合は、COMMITまたはROLLBACKを使用できます。どちらも、取得した読み取りロックを解放します。他の変更を行っていないため、同等です。


2
それらが同等であることを知らせてくれてありがとう。私の意見では、これが実際の質問に最もよく答えます。
chowey 2015年

実際の更新なしでコミットを使用すると、トランザクションが非アクティブになります。ライブサイトで直面したところ
Muhammad Omer Aslam

6

トランザクションを開始する場合、ベストプラクティスは常にそれをコミットすることです。use(transaction)ブロック内で例外がスローされた場合、トランザクションは自動的にロールバックされます。


3

トランザクションで読み取り専用クエリをラップすることは(特にJavaの場合)、トランザクションが「読み取り専用」であると伝えることができるため、JDBCドライバーはクエリの最適化を検討できます(ただし、そうする必要はないため、誰もINSERTそれにもかかわらずあなたが発行することを防ぎます)。たとえば、Oracleドライバは、読み取り専用のマークが付けられたトランザクションでのクエリのテーブルロックを完全に回避します。これにより、読み取りが多いアプリケーションで多くのパフォーマンスが得られます。


3

ネストされたトランザクションを検討してください。

ほとんどのRDBMSはネストされたトランザクションをサポートしていないか、非常に限られた方法でそれらをエミュレートしようとします。

たとえば、MS SQL Serverでは、内部トランザクション(実際のトランザクションではなく、MS SQL Serverはトランザクションレベルをカウントするだけです)でロールバックすると、最も外側のトランザクション(実際のトランザクション)で発生したすべてがロールバックされます。

一部のデータベースラッパーは、内部トランザクションでのロールバックを、エラーが発生したことを示す兆候と見なし、最も外側のトランザクションがコミットされたかロールバックされたかに関係なく、最も外側のトランザクションですべてをロールバックします。

したがって、COMMITは、コンポーネントが一部のソフトウェアモジュールで使用されていることを除外できない場合に安全な方法です。

これは質問に対する一般的な回答であることに注意してください。コード例では、新しいデータベース接続を開くことにより、外部トランザクションの問題を巧みに回避しています。

パフォーマンスに関して:分離レベルに応じて、SELECTにはさまざまな程度のロックと一時データ(スナップショット)が必要になる場合があります。これは、トランザクションが閉じられるときにクリーンアップされます。これがCOMMITまたはROLLBACKを介して行われるかどうかは関係ありません。費やされたCPU時間にはわずかな違いがあるかもしれません-COMMITはおそらくROLLBACK(2文字少ない)や他の小さな違いよりも解析が高速です。明らかに、これは読み取り専用操作にのみ当てはまります!

まったく求められていません。コードを読むことができるかもしれない別のプログラマーは、ROLLBACKがエラー状態を暗示すると想定するかもしれません。


2

余談ですが、次のようなコードを書くこともできます。

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

また、少しだけ再構成すると、IDataReaderのusingブロックを上に移動できる場合があります。


1

SQLをストアドプロシージャに入れ、これをクエリの上に追加すると、次のようになります。

set transaction isolation level read uncommitted

その後、C#コードのフープをジャンプする必要はありません。ストアドプロシージャでトランザクション分離レベルを設定しても、その接続の今後のすべての使用に設定が適用されるわけではありません(これは、接続がプールされるため、他の設定で心配する必要があることです)。ストアドプロシージャの最後に、接続が初期化されたものに戻ります。


1

ROLLBACKは主にエラーまたは例外的な状況の場合に使用され、COMMITは正常に完了した場合に使用されます。

重要ではないと思われる読み取り専用トランザクションの場合でも、COMMIT(成功の場合)およびROLLBACK(失敗の場合)を使用してトランザクションを閉じる必要があります。実際、一貫性と将来性を確保するために重要です。

読み取り専用トランザクションは、さまざまな方法で論理的に「失敗」する可能性があります。次に例を示します。

  • クエリが期待どおりに正確に1行を返さない
  • ストアドプロシージャが例外を発生させる
  • フェッチされたデータに不整合があることが判明しました
  • 時間がかかりすぎるため、ユーザーがトランザクションを中止した
  • デッドロックまたはタイムアウト

COMMITおよびROLLBACKが読み取り専用トランザクションで適切に使用されている場合、DB書き込みコードがキャッシング、監査、統計などのある時点で追加されても、期待どおりに機能します。

暗黙的なROLLBACKは、アプリケーションがクラッシュしたり、回復不能なエラー、ネットワーク障害、電源障害などで終了したりする「致命的なエラー」の状況でのみ使用してください。


0

READが状態を変更しない場合、何もしません。コミットを実行しても、リクエストをデータベースに送信するサイクルを無駄にする以外は何もしません。状態が変化した操作を実行していません。ロールバックについても同様です。

ただし、必ずオブジェクトをクリーンアップして、データベースへの接続を閉じてください。このコードが繰り返し呼び出される場合、接続を閉じないと問題が発生する可能性があります。


3
分離レベルに応じて、選択したCANは他のトランザクションをブロックするロックを取得できます。
Graeme Perrow、

接続はusingブロックの最後で閉じられます-それが目的です。しかし、ネットワークトラフィックがおそらく方程式の中で最も遅い部分であることは良い点です。
Joel Coehoorn、2008年

1
トランザクションは何らかの方法でコミットまたはロールバックされるため、成功した場合は常にコミットを発行することをお勧めします。
Neil Barnwell、

0

AutoCommitをfalseに設定すると、YESになります。

JDBC(Postgresqlドライバー)を使った実験で、選択クエリが中断すると(タイムアウトのため)、ロールバックしない限り、新しい選択クエリを開始できないことがわかりました。


-2

あなたのコードサンプルでは、

  1. //何か便利なことをする

    データを変更するSQLステートメントを実行していますか?

そうでない場合、「読み取り」トランザクションなどはありません...挿入、更新、削除ステートメント(データを変更する可能性のあるステートメント)からの変更のみがトランザクションにあります...話しているのはSQLのロックですそのデータに影響を与える他のトランザクションのために、サーバーはあなたが読んでいるデータを置きます。これらのロックのレベルは、SQL Server分離レベルに依存します。

ただし、SQLステートメントで何も変更されていない場合は、コミットまたはロールバックできません。

データを変更する場合は、トランザクションを明示的に開始せずに分離レベルを変更できます...個々のSQLステートメントはすべて暗黙的にトランザクション内にあります。トランザクションを明示的に開始する必要があるのは、2つ以上のステートメントが同じトランザクション内にあることを確認するためだけです。

トランザクション分離レベルを設定するだけの場合は、コマンドのCommandTextを "Set Transaction Isolation level Repeatable Read"(または任意のレベル)に設定し、CommandTypeをCommandType.Textに設定して、コマンドを実行します。(Command.ExecuteNonQuery()を使用できます)

注:複数の読み取りステートメントを実行していて、それらすべてに最初のデータベースと同じデータベースの状態を「参照」させたい場合は、分離レベルを設定する必要があります。


//有用な処理を行ってもデータは変更されず、読み取るだけです。私がやりたいのは、クエリの分離レベルを指定することだけです。
Stefan Moser

その後、クライアントから明示的にトランザクションを開始せずにそれを行うことができます... SQL文字列「Set Transaction Isolation Level ReadUncommitted」、「... Read Committed」、「... RepeatableRead」、「... Snapshot」を実行するだけですまたは「... Serializable」「Set Isolation Level Read Committed」
Charles Bretana 2008年

3
読んでいるだけでもトランザクションは重要です。複数の読み取り操作を行う場合は、トランザクション内で行うことで一貫性を確保できます。一人なしでそれらを行うことはできません。
MarkR 2008年

はい、申し訳ありません。正解です。少なくとも、分離レベルが反復可能読み取り以上に設定されている場合、これは当てはまります。
Charles Bretana、2008年

-3

他のユーザーが同じデータを読み取るのをブロックする必要がありますか?なぜトランザクションを使用するのですか?

@ジョエル-私の質問は、「なぜ読み取りクエリでトランザクションを使用するのですか?」

@Stefan-ストアドプロシージャではなくAdHoc SQLを使用する場合は、クエリのテーブルの後にWITH(NOLOCK)を追加するだけです。このようにして、トランザクションのアプリケーションとデータベースでオーバーヘッドは(最小限ではありますが)発生しません。

SELECT * FROM SomeTable WITH (NOLOCK)

編集@コメント3:質問タグに「sqlserver」が含まれていたため、MSSQLServerがターゲット製品であると想定していました。その点が明確になったので、タグを編集して特定の製品参照を削除しました。

そもそもなぜ読み取り操作でトランザクションを行いたいのか、私にはまだわかりません。


1
一度に設定した分離レベルに。トランザクションを使用して、クエリのロックの量を実際に減らすことができます。
Joel Coehoorn、2008年

1
トランザクションを使用して、より低い分離レベルを使用し、ロックを減らすことができます。
Stefan Moser

@StingyJack-このコードは複数の異なるデータベースに対して実行できるため、NOLOCKはオプションではありません。
Stefan Moser
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.