sql >> データベース >  >> RDS >> Database

コミットされていない分離レベルの読み取り

    [シリーズ全体のインデックスを参照]

    コミットされていない読み取りは、SQL標準で定義されている4つのトランザクション分離レベル(およびSQL Serverに実装されている6つのレベル)の中で最も弱いものです。これにより、3つのいわゆる「同時実行現象」すべてが可能になります。ダーティリード繰り返し不可の読み取り 、およびファントム:

    ほとんどのデータベースの人々は、少なくとも大まかにこれらの現象を認識していますが、提供されている分離保証を完全に説明していないことを誰もが認識しているわけではありません。また、SQLServerのような特定の実装で期待できるさまざまな動作を直感的に説明することもできません。これについては後で詳しく説明します。

    トランザクション分離–ACIDの「I」

    すべてのSQLコマンドは、トランザクション内で実行されます(明示的、暗黙的、または自動コミット)。すべてのトランザクションには関連付けられた分離レベルがあり、他の同時トランザクションの影響からどの程度分離されているかを決定します。このやや技術的な概念は、クエリの実行方法とクエリが生成する結果の品質に重要な影響を及ぼします。

    テーブル内のすべての行をカウントする単純なクエリについて考えてみます。このクエリを瞬時に(または同時データ変更なしで)実行できる場合、正解は1つだけです。つまり、その時点でテーブルに物理的に存在する行の数です。実際には、クエリの実行には一定の時間がかかり、結果は、データにアクセスするために選択された物理構造を通過するときに実行エンジンが実際に遭遇する行数によって異なります。

    カウント操作の進行中に同時トランザクションによって行がテーブルに追加(または削除)される場合、行カウントトランザクションがこれらの同時変更のすべて、一部、またはまったく発生しないかどうかによって、異なる結果が得られる可能性があります。次に、行カウントトランザクションの分離レベルに依存します。

    分離レベル、物理的な詳細、および同時操作のタイミングによっては、カウントトランザクションが、トランザクション中の任意の時点でテーブルのコミットされた状態を真に反映することのない結果を生成することさえあります。

    時間T1に開始し、テーブルを最初から最後までスキャンする行カウントトランザクションについて考えてみます(引数として、クラスター化インデックスキーの順序で)。その時点で、テーブルには100のコミットされた行があります。しばらくして(時間T2)、カウントトランザクションでこれらの行のうち50行が検出されました。同時に、並行トランザクションは2つの行をテーブルに挿入し、少し後の時刻T3(カウントトランザクションが終了する前)にコミットします。挿入された行の1つは、カウントトランザクションがすでに処理したクラスター化インデックス構造の半分に含まれ、もう1つの挿入された行はカウントされていない部分にあります。

    行カウントトランザクションが完了すると、このシナリオでは101行が報告されます。テーブル内の最初の100行と、スキャン中に検出された単一の挿入行。この結果は、テーブルのコミットされた履歴と矛盾しています。時間T1とT2で100のコミットされた行があり、次に時間T3で102のコミットされた行がありました。 101のコミットされた行があったことは一度もありませんでした。

    驚くべきことは(おそらく、これまでにこれらのことをどれだけ深く考えたかにもよるが)、この結果はデフォルトの(ロックされた)読み取りコミット分離レベルで、さらには繰り返し可能な読み取り分離の下でも可能であるということです。これらの分離レベルは両方とも、コミットされたデータのみを読み取ることが保証されていますが、データベースのコミットされた状態がないことを表す結果が得られました!

    分析

    同時実行の影響から完全に分離する唯一のトランザクション分離レベルは、シリアル化可能です。シリアル化可能な分離レベルのSQLServer実装は、データが最初にアクセスのためにロックされた時点で、トランザクションが最新のコミットされたデータを参照することを意味します。さらに、シリアル化可能な分離の下で検出されたデータのセットは、トランザクションが終了する前にそのメンバーシップを変更しないことが保証されています。

    行カウントの例は、データベース理論の基本的な側面を強調しています。同時変更が発生するデータベースにとって「正しい」結果が何を意味するかを明確にする必要があり、分離を選択するときに行うトレードオフを理解する必要があります。シリアル化可能よりも低いレベル。

    データベースのコミットされた状態のポイントインタイムビューが必要な場合は、スナップショットアイソレーション(トランザクションレベルの保証の場合)または読み取りコミットされたスナップショットアイソレーション(ステートメントレベルの保証の場合)を使用する必要があります。ただし、ポイントインタイムビューは、データベースの現在のコミットされた状態で動作しているとは限らないことに注意してください。実際には、古い情報を使用している可能性があります。一方、コミットされたデータのみに基づく結果に満足している場合は(おそらく異なる時点からのものであっても)、デフォルトのロッキング読み取りコミットされた分離レベルを維持することを選択できます。

    コミットされたデータの最新セットに基づいて確実に結果を生成する(そして決定を下す!)ために、データベースに対する操作のシリアル履歴については、シリアル化可能なトランザクション分離が必要になります。もちろん、このオプションは通常、リソースの使用と同時実行性の低下(デッドロックのリスクの増大を含む)の点で最も費用がかかります。

    行カウントの例では、両方のスナップショットアイソレーションレベル(SIとRCSI)で、ステートメント(この場合はトランザクション)の開始時にコミットされた行の数を表す100行の結果が得られます。読み取りコミットまたは繰り返し可能な読み取り分離をロックするときにクエリを実行すると、タイミング、ロックの粒度、行の挿入位置、および選択した物理アクセス方法に応じて、100、101、または102行の結果が生成される可能性があります。シリアル化可能な分離では、2つの同時トランザクションのどちらが最初に実行されたと見なされるかに応じて、結果は100行または102行になります。

    読み取りがコミットされていないことはどれほど悪いですか?

    使用可能な分離レベルの中で最も弱いものとして読み取り非コミット分離を導入したので、読み取りコミットのロック(次に高い分離レベル)よりもさらに低い分離保証を提供することを期待する必要があります。確かにそうです。しかし、問題は、読み取りコミットされた分離をロックするよりもどれだけ悪いのかということです。

    正しいコンテキストから始めるために、SQLServerのデフォルトのロック読み取りコミット分離レベルで発生する可能性のある主な同時実行効果のリストを次に示します。

    • コミットされた行がありません
    • 行が複数回検出されました
    • 単一のステートメント/クエリプランで検出された同じ行の異なるバージョン
    • 同じ行の異なる時点からのコミットされた列データ(例)

    これらの同時実行性の影響はすべて、データの読み取り時に非常に短期間の共有ロックのみを取得する読み取りコミットのロック実装によるものです。読み取りのコミットされていない分離レベルは、共有ロックをまったく取得しないことでさらに一歩進み、「ダーティ読み取り」の可能性がさらに高まります。

    ダーティリード

    簡単に言うと、「ダーティリード」とは、別の同時トランザクションによって変更されているデータの読み取りを指します(「変更」には、挿入、更新、削除、およびマージ操作が組み込まれます)。言い換えると、ダーティリードは、変更トランザクションがそれらの変更をコミットまたは中止する前に、トランザクションが別のトランザクションが変更したデータを読み取るときに発生します。

    長所と短所

    読み取り非コミット分離の主な利点は、互換性のないロック(ロックのエスカレーションによる不要なブロックを含む)によるブロックとデッドロックの可能性の減少、およびパフォーマンスの向上(共有ロックの取得と解放の必要性の回避による)です。

    コミットされていない分離の読み取りの最も明らかな潜在的な欠点は、(名前が示すように)コミットされていないデータ(決してされていないデータであっても)を読み取る可能性があることです。 トランザクションロールバックの場合はコミットされます)。ロールバックが比較的まれなデータベースでは、コミットされていないデータを読み取る問題は、問題のデータが確実にある段階で、おそらくすぐにコミットされるため、単なるタイミングの問題と見なされる可能性があります。行カウントの例(より高い分離レベルで動作していた)では、タイミングに関連する不整合がすでに見られたため、「早すぎる」データを読み取ることがどれほど懸念されるのか疑問に思われるかもしれません。

    明らかに答えは地域の優先順位と文脈に依存しますが、読み取りのコミットされていない分離を使用するという情報に基づいた決定は確かに可能であるように思われます。しかし、もっと考えることがあります。コミットされていない読み取り分離レベルのSQLServer実装には、「情報に基づいた選択」を行う前に知っておく必要のある微妙な動作が含まれています。

    割り当て順序スキャン

    SQL Serverは、読み取りのコミットされていない分離を使用することで、割り当て順序のスキャンの結果として発生する可能性のある不整合を受け入れる準備ができていることを示します。

    通常、ストレージエンジンは、基になるデータが変更されないことが保証されている場合にのみ、割り当て順序のスキャンを選択できます。 スキャン中(たとえば、データベースが読み取り専用であるか、テーブルロックのヒントが指定されているため)。ただし、読み取りのコミットされていない分離が使用されている場合、基盤となるデータが同時トランザクションによって変更される可能性がある場合でも、ストレージエンジンは割り当て順スキャンを選択する可能性があります。

    このような状況では、割り当て順スキャンにより、一部のコミット済みデータが完全に失われたり、他のコミット済みデータに複数回遭遇したりする可能性があります。 コミットの欠落または二重カウントに重点が置かれています データ(コミットされていないデータを読み取らない)なので、「ダーティリード」自体の場合ではありません。この設計上の決定(コミットされていない読み取りの分離の下で割り当て順序のスキャンを許可する)は、かなり物議を醸すものとして一部の人々に見られています。

    注意点として、コミットされた行が欠落したり二重にカウントされたりする一般的なリスクは、コミットされていない分離を読み取ることに限定されないことを明確にする必要があります。コミットされた読み取りと繰り返し可能な読み取りをロックすることで同様の効果を確認することは確かに可能ですが(前に見たように)、これは別のメカニズムを介して発生します。コミットされた行が欠落しているか、データの変更に対する割り当て順序のスキャンが原因で複数回発生している 読み取り非コミット分離の使用に固有です。

    「破損した」データの読み取り

    ロジックに反するように見える結果(さらには制約をチェックすることさえも!)は、読み取りコミットされた分離をロックすることで可能です(ここでも、いくつかの例についてはCraig Freedmanによるこの記事を参照してください)。要約すると、重要なのは、読み取りコミットをロックすると、さまざまな時点からコミットされたデータを表示できるということです。たとえば、クエリプランでインデックスの交差などの手法を使用している場合は、単一の行でも表示されます。

    これらの結果は予期しないものである可能性がありますが、コミットされたデータのみを読み取るという保証と完全に一致しています。より高いデータ整合性の保証にはより高い分離レベルが必要であるという事実から逃れることはできません。

    これらの例は、これまでに見たことがない場合でも、非常に衝撃的なものになる可能性があります。もちろん、読み取りのコミットされていない分離でも同じ結果が得られますが、ダーティ読み取りを許可すると、追加の次元が追加されます。結果には、同じ行であっても、異なる時点からのコミットされたデータとコミットされていないデータが含まれる場合があります。

    さらに進んで、コミットされていないトランザクションの読み取りで単一列の値を読み取ることも可能です。 コミットされたデータとコミットされていないデータが混在した状態。これは、値が複数のデータページに保存されている場合に、LOB値(たとえば、xml、または「max」タイプのいずれか)を読み取るときに発生する可能性があります。コミットされていない読み取りは、異なるページの異なる時点からのコミットされたデータまたはコミットされていないデータに遭遇する可能性があり、値の混合である最終的な単一列の値になります!

    例として、最初に10,000個の「x」文字を含む単一のvarchar(max)列について考えてみます。並行トランザクションは、この値を10,000'y'文字に更新します。コミットされていない読み取りトランザクションは、LOBの1つのページから「x」文字を読み取り、別のページから「y」文字を読み取ることができ、「x」文字と「y」文字の混合を含む最終的な読み取り値になります。これが「破損した」データの読み取りを表すものではないと主張するのは難しいです。

    デモ

    LOBデータの単一行を使用してクラスター化テーブルを作成します。

    CREATE TABLE dbo.Test
    (
        RowID integer PRIMARY KEY,
        LOB varchar(max) NOT NULL,
    );
     
    INSERT dbo.Test
        (RowID, LOB)
    VALUES
        (1, REPLICATE(CONVERT(varchar(max), 'X'), 16100));

    別のセッションで、次のスクリプトを実行して、コミットされていない分離の読み取り時にLOB値を読み取ります。

    -- Run this in session 2
    SET NOCOUNT ON;
     
    DECLARE 
        @ValueRead varchar(max) = '',
        @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
        @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
     
    WHILE 1 = 1
    BEGIN
        SELECT @ValueRead = T.LOB
        FROM dbo.Test AS T WITH (READUNCOMMITTED)
        WHERE T.RowID = 1;
     
        IF @ValueRead NOT IN (@AllXs, @AllYs)
        BEGIN
        	PRINT LEFT(@ValueRead, 8000);
            PRINT RIGHT(@ValueRead, 8000);
            BREAK;
        END
    END;

    最初のセッションで、次のスクリプトを実行して、LOB列に交互の値を書き込みます。

    -- Run this in session 1
    SET NOCOUNT ON;
     
    DECLARE 
        @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
        @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
     
    WHILE 1 = 1
    BEGIN
        UPDATE dbo.Test
        SET LOB = @AllYs
        WHERE RowID = 1;
     
        UPDATE dbo.Test
        SET LOB = @AllXs
        WHERE RowID = 1;
    END;

    しばらくすると、セッション2のスクリプトが終了し、LOB値の混合状態が読み取られます(例:

    )。

    この特定の問題は、複数のページにまたがるLOB列値の読み取りに限定されます。これは、分離レベルによって提供される保証のためではなく、SQLServerがページレベルのラッチを使用して物理的な整合性を確保するためです。この実装の詳細の副作用は、単一の読み取り操作のデータがたまたま単一のページに存在する場合に、そのような「破損した」データ読み取りを防ぐことです。

    使用しているSQLServerのバージョンに応じて、xml列の「混合状態」データが読み取られると、xmlの結果が不正である可能性があるためにエラーが発生するか、エラーがまったく発生しないか、コミットされていない特定のエラー601が発生します。 、「データ移動のため、NOLOCKでスキャンを続行できませんでした。」他のLOBタイプの混合状態データを読み取っても、通常はエラーメッセージは表示されません。消費するアプリケーションまたはクエリには、最悪の種類のダーティリードが発生したことを知る方法がありません。分析を完了するために、インデックスの交差の結果として読み取られた非LOB混合状態行が、エラーとして報告されることはありません。

    ここでのメッセージは、コミットされていない読み取り分離を使用する場合、ダーティ読み取りには「破損した」混合状態LOB値を読み取る可能性が含まれることを受け入れることです。

    NOLOCKヒント

    少なくともこの(広く乱用され誤解されている)表のヒントに言及しない限り、読み取りのコミットされていない分離レベルの説明は完了しないと思います。ヒント自体は、READUNCOMMITTEDテーブルヒントの同義語にすぎません。それはまったく同じ機能を実行します:それが適用されるオブジェクトは、(例外はありますが)コミットされていない分離セマンティクスの読み取りを使用してアクセスされます。

    「NOLOCK」という名前に関する限り、これは単にデータの読み取り時に共有ロックが使用されないことを意味します 。その他のロック(スキーマの安定性、データ変更専用のロックなど)は引き続き通常どおり使用されます。

    一般的に、NOLOCKヒントは、SERIALIZABLEやREADCOMMITTEDLOCKなどの他のオブジェクトごとの分離レベルのテーブルヒントとほぼ同じくらい一般的である必要があります。つまり、あまり一般的ではなく、適切な代替手段がなく、明確な目的があり、完全な場合にのみ使用されます。 結果の理解。

    NOLOCK(またはREADUNCOMMITTED)の正当な使用例の1つは、DMVまたは他のシステムビューにアクセスする場合です。この場合、分離レベルが高くなると、非ユーザーデータ構造で不要な競合が発生する可能性があります。もう1つのエッジケースの例は、クエリが大きなテーブルの重要な部分にアクセスする必要がある場合です。これにより、ヒント付きクエリの実行中にデータが変更されることはありません。代わりにスナップショットまたは読み取りコミットスナップショットアイソレーションを使用しない正当な理由が必要であり、期待されるパフォーマンスの向上をテスト、検証し、たとえば単一の共有テーブルロックヒントを使用して比較する必要があります。

    NOLOCKの最も望ましくない使用法は、残念ながら最も一般的な使用法です。つまり、クエリ内のすべてのオブジェクトに、一種の高速な魔法のスイッチとして適用します。世界最高の意志で、SQLServerコードを明らかに素人っぽく見せるためのより良い方法はありません。クエリ、コードブロック、またはモジュールの読み取りのコミットされていない分離が合法的に必要な場合は、セッション分離レベルを適切に設定し、アクションを正当化するコメントを提供することをお勧めします。

    最終的な考え

    コミットされていない読み取りは、トランザクション分離レベルの正当な選択ですが、情報に基づいた選択である必要があります。念のため、SQLServerのデフォルトのロック読み取りコミット分離で発生する可能性のある同時実行現象の一部を次に示します。

    • 以前にコミットされた行がありません
    • コミットされた行が複数回検出されました
    • 単一のステートメント/クエリプランで検出された同じ行の異なるコミットバージョン
    • 同じ行(ただし異なる列)の異なる時点からのコミットされたデータ
    • 有効化およびチェックされた制約と矛盾するように見えるコミットされたデータ読み取り

    見方によっては、これはデフォルトの分離レベルで発生する可能性のある不整合の非常に衝撃的なリストになる可能性があります。そのリストに、コミットされていない分離の追加を読んでください:

    • ダーティリード(まだコミットされていない、またはコミットされない可能性のあるデータに遭遇)
    • コミットされたデータとコミットされていないデータが混在する行
    • 割り当て順序のスキャンが原因で行が欠落/重複している
    • 混合状態(「破損」)の個々の(単一列)LOB値
    • エラー601–「データ移動のためにNOLOCKでスキャンを続行できませんでした」(例)。

    トランザクションの主な懸念事項が、読み取りコミットされた分離のロックの副作用(ブロッキング、ロックオーバーヘッド、ロックエスカレーションによる同時実行性の低下など)に関する場合は、読み取りコミットスナップショット分離などの行バージョン管理分離レベルの方が適している可能性があります。 (RCSI)またはスナップショットアイソレーション(SI)。ただし、これらは無料ではありません。特にRCSIでの更新には、直感に反する動作があります。

    非常に高いレベルの一貫性の保証を必要とするシナリオでは、シリアライズ可能が唯一の安全な選択です。読み取り専用データに対するパフォーマンスが重要な操作(たとえば、ETLウィンドウ間で効果的に読み取り専用である大規模なデータベース)の場合は、データベースを明示的にREAD_ONLYに設定することも適切です(データベースが読み取り専用であり、不整合のリスクはありません。

    また、読み取りのコミットされていない分離が正しい選択であるアプリケーションの数は比較的少なくなります。これらのアプリケーションは、おおよその結果と、時折一貫性がない、明らかに無効な(制約の観点から)、または「おそらく破損した」データの可能性に満足している必要があります。データが比較的頻繁に変更されない場合、これらの不整合のリスクもそれに応じて低くなります。

    [シリーズ全体のインデックスを参照]


    1. node-postgresを使用して複数の行をPGに適切に挿入するにはどうすればよいですか?

    2. すべての着信接続を受け入れるようにPostgreSQLを構成する方法

    3. データベースから接続文字列を取得する方法

    4. PostgreSQLインストールのバイナリ形式で書き込み可能なmongo_fdw拡張機能をコンパイルします。