SQL Serverは、読み取りコミットの2つの物理的な実装を提供します SQL標準で定義された分離レベル、読み取りコミットおよび読み取りコミットスナップショットアイソレーションのロック( RCSI )。どちらの実装も、読み取りコミットの分離動作に関するSQL標準に定められた要件を満たしていますが、RCSIの物理的な動作は、このシリーズの前回の投稿で見たロックの実装とはまったく異なります。
論理的保証
SQL標準では、読み取りコミット分離レベルで動作するトランザクションでダーティ読み取りが発生しないことが要求されています。この要件を表す別の方法は、コミットされた読み取りトランザクションがコミットされたデータのみに遭遇する必要があると言うことです。 。
この規格では、コミットされたトランザクションの読み取りが可能性があるとされています。 繰り返し不可能な読み取りおよびファントムとして知られる同時実行現象を体験します(実際にはそうする必要はありませんが)。たまたま、SQL Serverでの読み取りコミット分離の両方の物理実装では、正確な詳細はまったく異なりますが、繰り返し不可能な読み取りとファントム行が発生する可能性があります。
コミットされたデータの特定時点のビュー
データベースオプションREAD_COMMITTED_SNAPSHOT
の場合 ON
で 、SQL Serverは、読み取りコミット分離レベルの行バージョン管理実装を使用します。これを有効にすると、読み取りコミット分離を要求するトランザクションは自動的にRCSI実装を使用します。 RCSIを使用するために、既存のT-SQLコードを変更する必要はありません。これは同じではないことに注意してください コードが同じように動作すると言っているように RCSIでは、読み取りコミットのロック実装を使用する場合と同様に、実際、これは一般的にそうではありません 。
SQL標準には、読み取りコミットされたトランザクションによって読み取られるデータが最近であることを要求するものはありません。 コミットされたデータ。 SQL Server RCSIの実装では、これを利用して、トランザクションに特定時点のビューを提供します。 コミットされたデータの、その時点が現在のステートメントが始まった瞬間 実行(含まれているトランザクションが開始された瞬間ではありません)。
これは、読み取りコミットのSQL Serverロック実装の動作とはまったく異なります。この場合、ステートメントは、各アイテムが物理的に読み取られた瞬間の時点で最も最近コミットされたデータを参照します。 。読み取りコミットをロックすると、共有ロックが可能な限り迅速に解放されるため、検出されるデータのセットは非常に異なる時点からのものである可能性があります。
要約すると、コミットされた読み取りをロックすると、各行が表示されます 当時のように、一時的にロックされ、物理的に読み取られました。 RCSIはすべての行を確認します 声明が始まったときのように。どちらの実装も、コミットされていないデータが表示されないことが保証されていますが、検出されるデータは大きく異なる可能性があります。
ポイントインタイムビューの意味
コミットされたデータの特定の時点のビューを見ると、ロックの実装のより複雑な動作よりも自明のように優れているように見える場合があります。たとえば、ポイントインタイムビューが行の欠落の問題に悩まされることはないことは明らかです。 または同じ行に複数回遭遇する 、どちらも読み取りコミットされた分離をロックすることで可能です。
RCSIの2つ目の重要な利点は、共有ロックを取得しないことです。 データを読み取る場合、データは直接アクセスされるのではなく、行バージョンストアから取得されるためです。共有ロックがないため、同時実行性が大幅に向上します 互換性のないロックを取得しようとしている同時トランザクションとの競合を排除することによって。この利点は、一般的に、リーダーはRCSIの下でライターをブロックしない、またはその逆であると言うことで要約されます。互換性のないロック要求によるブロッキングを減らすことのさらなる結果として、デッドロックの機会 通常、RCSIで実行すると大幅に削減されます。
ただし、これらのメリットは、コストと注意事項なしでは実現できません。 。 1つには、コミットされた行のバージョンを維持するとシステムリソースが消費されるため、主に tempdb の観点から、これに対処するように物理環境を構成することが重要です。 パフォーマンスとメモリ/ディスクスペースの要件。
2つ目の注意点は、もう少し微妙です。RCSIは、コミットされたデータのスナップショットビューをそのまま提供します。 ステートメントの開始時ですが、RCSIステートメントの実行中に実際のデータが変更される(およびそれらの変更がコミットされる)ことを妨げるものは何もありません。共有ロックはありません。覚えておいてください。この2番目のポイントの直接の結果は、RCSIで実行されているT-SQLコードが古い情報に基づいて決定を下す可能性があることです。 、データベースの現在のコミット状態と比較。これについては後ほど詳しく説明します。
先に進む前に、RCSIについて最後に(実装固有の)観察を行いたいと思います。 スカラーおよびマルチステートメント関数 包含ステートメントとは異なる内部T-SQLコンテキストを使用して実行します。これは、スカラーまたはマルチステートメント関数の呼び出し内に表示されるポイントインタイムビューが、ステートメントの残りの部分に表示されるポイントインタイムビューよりも遅くなる可能性があることを意味します。同じステートメントの異なる部分が異なる時点のデータを参照するため、これにより予期しない不整合が発生する可能性があります。 。この奇妙で紛らわしい振る舞いはありません インライン関数に適用されます。インライン関数は、それらが表示されるステートメントと同じスナップショットを表示します。
繰り返し不可能な読み取りとファントム
データベースのコミットされた状態のステートメントレベルのポイントインタイムビューを考えると、RCSIでの読み取りコミットされたトランザクションが、繰り返し不可能な読み取りまたはファントム行の現象をどのように経験するかがすぐにはわからない場合があります。確かに、私たちの思考を単一のステートメントの範囲に限定すると 、これらの現象はいずれもRCSIでは発生しません。
同じステートメント内で同じデータを複数回読み取る RCSIでは、常に同じデータ値が返され、それらの読み取りの間にデータが消えることはなく、新しいデータも表示されません。どのような種類のステートメントが同じデータを複数回読み取る可能性があるのか疑問に思っている場合は、おそらくサブクエリで、同じテーブルを複数回参照するクエリについて考えてみてください。
ステートメントレベルの読み取りの一貫性は、データの固定スナップショットに対して発行された読み取りの明らかな結果です。 RCSIがしない理由 繰り返し不可能な読み取りやファントムからの保護を提供するのは、これらのSQL標準現象がトランザクションレベルで定義されていることです。 RCSIで実行されているトランザクション内の複数のステートメントは、異なるデータを表示する場合があります。これは、各ステートメントがその特定のステートメントの時点での特定の時点のビューを表示するためです。 開始しました。
要約すると、各ステートメント RCSIトランザクション内では、静的にコミットされたデータセットが表示されますが、そのセットは同じトランザクション内のステートメント間で変更される可能性があります。
古いデータ
T-SQLコードが古い情報に基づいて重要な決定を下す可能性は、少し不安を感じるだけではありません。 RCSIで実行されている単一のステートメントで使用されるポイントインタイムスナップショットが任意に古い可能性があることを少し考えてみてください。 。
かなりの期間実行されるステートメントは、ステートメントが開始されたときのデータベースのコミット状態を引き続き確認します。その間、ステートメントには、それ以降にデータベースで発生したすべてのコミットされた変更がありません。
これは、RCSIでの古いデータへのアクセスに関連する問題が長期的なに限定されているということではありません。 声明ですが、そのような場合、問題は確かにより顕著になる可能性があります。
タイミングの質問
この古いデータの問題は、どれほど迅速に完了するかに関係なく、原則としてすべてのRCSIステートメントに適用されます。時間枠がどれほど小さくても、同時操作によって、その変更に気付かずに、作業中のデータセットが変更される可能性が常にあります。読み取りコミットのロックの動作を調査するときに以前に使用した簡単な例の1つをもう一度見てみましょう。
INSERT dbo.OverdueInvoices SELECT I.InvoiceNumber FROM dbo.Invoices AS I WHERE I.TotalDue > ( SELECT SUM(P.Amount) FROM dbo.Payments AS P WHERE P.InvoiceNumber = I.InvoiceNumber );
RCSIで実行する場合、このステートメントはできません ステートメントの実行開始後に発生するコミットされたデータベースの変更を確認してください。ロックの実装で発生する可能性のある行の欠落や複数回の発生の問題は発生しませんが、同時トランザクションにより、必要な支払いが追加される可能性があります。 上記のステートメントの実行が開始された後、顧客に支払いの延滞に関する厳しい警告レターが送信されないようにするため。
このシナリオ、または概念的に類似している他のシナリオで発生する可能性のある他の多くの潜在的な問題について考えることができます。ステートメントの実行時間が長くなるほど、データベースのビューが古くなり、意図しない結果が生じる可能性が高くなります。
もちろん、この特定の例には多くの緩和要因があります。この動作は、完全に許容できるものと見なされる可能性があります。結局のところ、支払いが数秒遅れて到着したためにリマインダーレターを送信することは、簡単に防御できるアクションです。ただし、原則は変わりません。
ビジネスルールの失敗と整合性のリスク
数秒早く警告レターを送信するよりも、古い情報を使用すると、より深刻な問題が発生する可能性があります。このクラスの弱点の良い例は、トリガーコードで見ることができます。 宣言型の参照整合性制約を適用するには複雑すぎる可能性がある整合性ルールを適用するために使用されます。説明のために、トリガーを使用して外部キー制約のバリエーションを強制するが、特定の子テーブル行に対してのみ関係を強制する次のコードについて考えてみます。
ALTER DATABASE Sandpit SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE TABLE dbo.Parent (ParentID integer PRIMARY KEY); GO CREATE TABLE dbo.Child ( ChildID integer IDENTITY PRIMARY KEY, ParentID integer NOT NULL, CheckMe bit NOT NULL ); GO CREATE TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END; GO -- Insert parent row #1 INSERT dbo.Parent (ParentID) VALUES (1);を挿入します
次に、親行#1を削除するが、まだコミットしていない別のセッションで実行されているトランザクション(フォローしている場合は別のSSMSウィンドウを使用)について考えてみます。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN TRANSACTION; DELETE FROM dbo.Parent WHERE ParentID = 1;
元のセッションに戻って、この親を参照する(チェックされた)子行を挿入しようとします:
INSERT dbo.Child (ParentID, CheckMe) VALUES (1, 1);
トリガーコードは実行されますが、RCSIはコミット済みしか認識しないためです。 ステートメントが開始された時点のデータでは、(コミットされていない削除ではなく)親行が引き続き表示され、挿入は成功します !
親行を削除したトランザクションは、変更を正常にコミットできるようになり、データベースは不整合のままになります。 トリガーロジックの観点からの状態:
COMMIT TRANSACTION; SELECT P.* FROM dbo.Parent AS P; SELECT C.* FROM dbo.Child AS C;
これはもちろん単純化された例であり、組み込みの制約機能を使用して簡単に回避できる例です。はるかに複雑なビジネスルールと疑似整合性制約を、トリガーの内部と外部に書き込むことができます。 。 RCSIでの誤った動作の可能性は明らかです。
ブロック動作と最新のコミットデータ
T-SQLコードは、ロックの実装を使用した場合と同じようにRCSI読み取りコミットで動作することが保証されていないことを前述しました。上記のトリガーコードの例はその良い例ですが、一般的な問題はトリガーに限定されないことを強調する必要があります。 。
RCSIは通常、コミットされていない変更が同時に存在する場合にその正確性がブロッキングに依存するT-SQLコードには適していません。コードが現在の読み取りに依存している場合、RCSIも正しい選択ではない可能性があります ステートメント開始時の最新のコミット済みデータではなく、コミット済みデータ。これらの2つの考慮事項は関連していますが、同じものではありません。
RCSIでコミットされた読み取りのロック
SQL Serverは、ロックを要求する1つの方法を提供します テーブルヒントREADCOMMITTEDLOCK
を使用して、RCSIが有効になっているときにコミットされた読み取り 。正しく実行するためにブロッキング動作が必要なテーブルにこのヒントを追加することで、上記の問題を回避するようにトリガーを変更できます。
ALTER TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P WITH (READCOMMITTEDLOCK) -- NEW!! ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END;
この変更を行うと、削除トランザクションがコミット(または中止)するまで、孤立している可能性のある子行ブロックを挿入しようとします。削除がコミットされると、トリガーコードは整合性違反を検出し、予期されるエラーを発生させます。
正しく実行されない可能性があるクエリを特定する RCSIの下では、広範なテストが必要になる可能性のある重要なタスクです。 正しく理解するために(これらの問題は非常に一般的であり、トリガーコードに限定されないことを忘れないでください!)また、READCOMMITTEDLOCK
を追加します それを必要とするすべてのテーブルへのヒントは、退屈でエラーが発生しやすいプロセスになる可能性があります。 SQL Serverが、必要に応じてロックの実装を要求するためのより広範囲のオプションを提供するまで、テーブルヒントの使用に固執します。
次回
このシリーズの次の投稿では、RCSIでのデータ変更ステートメントの驚くべき動作を見て、読み取りコミットスナップショットアイソレーションの調査を続けます。
[シリーズ全体のインデックスを参照]