SQL Server 2008 R2 Service Pack 2の累積的な更新プログラム11に含まれている修正の1つは、特定のシナリオで発生する可能性のある「誤ったデッドロック」に対処します(この記事の後半で説明します)。残念ながら、この修正により新しいバグが発生し、RCSI(コミットされたスナップショットアイソレーションの読み取り)でのSELECTクエリがテーブルレベルのインテント共有ロックの取得を開始します。その結果、2008 R2 SP2 CU11(またはそれ以降)を適用した後、RCSIクエリのブロックが増加する(およびデッドロックが発生する可能性があります)場合があります。
これは、RCSIを使用しているときに、リーダーがライターをブロックしない(またはその逆)ことに慣れている人にとっては、歓迎されない驚きです。執筆時点では、RCSIのバグに対する修正はありません。実際、この問題を報告するためにEugene Karpovichによって作成されたConnectアイテムは、「修正されません」としてクローズされましたが、この決定は現在検討中であると理解しています。
通常、この問題はそれほど大きな問題ではない可能性があります。これは、累積的な更新は通常、フルサービスパックほど広く適用されていないためです。ただし、Microsoftは最近、SQL Server2008R2用のFinalServicePack3があることを発表しました。このサービスパックは、既存のSP2累積更新(CU13まで)の単純なロールアップですが、新しい修正はありません。このすべての結果として、その間に何かが変更されない限り、SP3を適用しているユーザーは、CU11で導入されたRCSIバグの影響を受け始めます。
編集:この記事が公開される直前に、MicrosoftはこのリグレッションがSP3で修正されることを確認しました。
KB2923460で説明されているように、同じ「誤ったデッドロック」バグ(修正により新しいバグが導入されます)もSQL Server 2012 ServicePack1の累積的な更新8で修正されました。 SQL Server 2012の修正は異なり、ありません 新しいRCSI問題を紹介します。
私の知る限り、SQLServer2014はどちらの問題の影響も受けませんでした。それ以外のことを示すドキュメントは確かにありません。2014RTM、CU1、およびCU2で実行したテストでは、どちらのバグも再現されていません。
2008R2RCSIバグ
RCSIで実行されるSELECTクエリは、通常、スキーマ安定性(Sch-S)ロックのみを取ります。これは、スキーマ変更(Sch-M)ロックを除く他のすべてのロックと互換性があります。 CU11(またはそれ以降)がSQL Server 2008 R2インスタンスに適用されると、これらのクエリはテーブルレベルのインテント共有(Tab-IS)ロックを取得し始めます。次のテストスクリプトを使用して、動作の違いを示すことができます。
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET NOCOUNT ON; GO CREATE DATABASE RCSI; GO ALTER DATABASE RCSI SET READ_COMMITTED_SNAPSHOT ON; GO ALTER DATABASE RCSI SET ALLOW_SNAPSHOT_ISOLATION OFF; GO USE RCSI; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1), (2), (3), (4); GO -- Show locks DBCC TRACEON (1200, 3604, -1) WITH NO_INFOMSGS; SELECT * FROM dbo.Test; DBCC TRACEOFF (1200, 3604, -1) WITH NO_INFOMSGS; GO ALTER DATABASE RCSI SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE RCSI;
バグなしでSQLServer2008 R2のインスタンスに対して実行すると、デバッグ出力には、期待どおりにテストステートメントに対して取得された単一のSch-Sロックが表示されます。
OBJECTのSch-Sロックを取得するプロセス:7:2105058535:0結果:OKOBJECTのロックを解放するプロセス:7:2105058535:0
SQL Server 2008 R2ビルド10.50.4302(またはそれ以降)に対して実行すると、出力は次のようになります。
オブジェクトのISロックを取得するプロセス:7:2105058535:0結果:OKオブジェクトのロックを解放するプロセス:7:2105058535:0
Sch-SロックがTab-ISロックに置き換えられていることに注意してください。
影響と緩和策
インテント共有(IS)ロックは依然として非常に互換性のあるロックですが、Sch-Sほど並行性に優れているわけではありません。ロック互換性マトリックスは、ISロックが以下と競合することを示しています:
- Sch-M(スキーマ修正)–Sch-Sによる
- BU(一括更新)
- X(排他的)
排他的(X)ロックとの非互換性は、並行プロセスが同じリソースに対して排他的ロックを保持している場合、RCSIでの読み取りがブロックされることを意味します。同様に、排他ロックを必要とするライターは、同時RCSIリーダーがISロックを保持している場合にブロックします。データが変更されるたびに排他的ロックが取得され、トランザクションの最後まで保持されるため、バグの影響により、CU11が適用される前に、RCSIのリーダーが同時ライターによってブロックされます(またはその逆)。
重要な緩和要因は、バグがテーブルレベルのみを引き起こすことです。 取得するインテント共有ロック。 テーブルレベルを必要とする並行ライター 排他的ロックはブロッキング(および潜在的にデッドロック)を引き起こします。ただし、下位(行、ページ、パーティションなど)レベルでの排他的ロックのみを必要とする同時ライターは必要ありません ブロッキングまたはデッドロックを引き起こします。テーブルレベルでは、これらのライターは、Tab-ISと互換性のあるインテントエクスクルーシブ(IX)ロックのみを取得します。より低いレベルの粒度で取得された排他ロックは、競合を引き起こしません。
ほとんどのシステムでは、テーブルレベルの排他的(Tab-X)ロックは比較的まれです。 TABLOCKXヒントを使用して明示的に要求されない限り、Tab-Xロックの考えられる原因は次のとおりです。
- より低い粒度からのロックエスカレーション
- キー範囲ロックのサポートインデックスなしでSERIALIZABLEを使用する
技術的な回避策は、(冗長な)テーブルヒントWITH (READCOMMITTED)
を追加することです。 RCSIで実行されるすべてのクエリのすべてのテーブルに。これはたまたまバグを回避するため、Sch-Sロックのみが取得されますが、実用的な提案とは言えません。
これらの緩和策にもかかわらず、RCSIでの読み取り専用クエリにTab-ISを使用することは、依然として誤った動作です。 ServicePack3がリリースされる前にSQLServer2008R2で修正できることを願っています。
「誤ったデッドロック」バグ
前述のように、RCSIバグは、「誤ったデッドロック」バグの修正の副作用として導入されました。この以前の問題は、KB2929464のSQL Server2008R2およびKB2923460のSQLServer2012で文書化されています。どちらのドキュメントも明快さ(または正確さ)のモデルではありませんが、根本的な問題は非常に興味深いので、ここで少し時間をかけて見ていきたいと思います。
基本的に、デッドロックは次の場合に発生します。
- 同じテーブルから読み取られた3つ以上の同時トランザクション
- UPDLOCKおよびTABLOCKヒントは、3つのケースすべてで使用されます
- データベース設定READ_COMMITTED_SNAPSHOTがオンになっています
トランザクションがどの分離レベルで実行されるかは重要ではないことに注意してください。バグを再現するには、最初に以下のセットアップスクリプトを実行します。
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE DATABASE IncorrectDeadlock; GO ALTER DATABASE IncorrectDeadlock SET READ_COMMITTED_SNAPSHOT ON; GO USE IncorrectDeadlock; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1);
次に、3つの別々の接続で次のスクリプトを実行します(トランザクションは開いたままであることに注意してください):
USE IncorrectDeadlock; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO BEGIN TRANSACTION; SELECT T.id, T.col1 FROM dbo.Test AS T WITH (UPDLOCK, TABLOCK);
この時点で、最初のセッションは結果セットを返し、他の2つはブロックされます。 「誤ったデッドロック」は、最初のセッションがトランザクション(コミットまたはロールバック)を完了するときに発生します。これが発生すると、他の2つのセッションのいずれかでデッドロックが報告されます:
デッドロックが発生するのは、以前にブロックされた2つのセッションがTab-IX(テーブルレベルのインテント排他的)を保持し、両方がロックをTab-X(テーブルレベルの排他的)に変換する必要があるためです。 Tab-IXは別のTab-IXと互換性がありますが、Tab-Xとは互換性がありません。これは変換のデッドロックです(皮肉なことに、変換のデッドロックを回避するためにUPDLOCKがよく使用されます)。
必要に応じて、3つのクエリのトランザクション分離レベルを自由に変更してください。デッドロックは、RCSIが有効になっている限り、同じロックが関係している場合でも発生します。テストが完了したら、テストデータベースを削除します。
USE IncorrectDeadlock; ALTER DATABASE IncorrectDeadlock SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE IncorrectDeadlock;
分析と説明
個人的には、コードでUPDLOCKとTABLOCKを一緒に使用したことを覚えていません。 SQL Serverにはテーブルレベルの更新ロックがないため、私には、このヒントの組み合わせは直感的に奇妙に思えます。 。それで、それは意味でさえ何を意味しますか? UPDLOCKヒントとTABLOCKヒントを一緒に指定するには?
ドキュメントには次のように書かれています:
アップロックトランザクションが完了するまで更新ロックを取得して保持することを指定します。 UPDLOCKは、行レベルまたはページレベルでのみ読み取り操作の更新ロックを取得します。 UPDLOCKがTABLOCKと組み合わされている場合、または他の理由でテーブルレベルのロックが取得されている場合は、代わりに排他的(X)ロックが取得されます。
これは、ヒントの組み合わせによって単一の排他テーブルロックが発生することを示しています。実際、これがすべてではありません。
SQL Server 2000では、UPDLOCKヒントとTABLOCKヒントを組み合わせると、Tab-S(共有テーブルロック)が取得され、READ UNCOMMITTEDを除くすべての分離レベルでTab-X(排他テーブルロック)に変換されます。この一連のロックにより、3つ以上のセッションが関与するデッドロックが発生する可能性があります。2つのセッションがTab-Sを取得し、両方がもう一方のセッションを待機してTab-Xに変換します。 READ UNCOMMITTEDでは、SQL Server 2000はSch-S、次にTab-Xを使用しますが、デッドロックは発生しません(通常のブロックのみ)。
SQL Server 2005以降(バグ修正なし)では、取得されるロックはのみに依存します。 RCSIが有効かどうかについて。 RCSIが有効になっている場合、すべての分離レベル Tab-IXを取得してから、Tab-Xに変換します。このシーケンスにより、バグ修正アドレスのデッドロックが発生します。
RCSIが有効になっていない場合、一致する分離レベルはSQL Server 2000の場合と同じように動作します(Tab-Sを取得してからTab-Xに変換します)。 (2005年の新機能)スナップショット分離レベルでは、Sch-Sの後にTab-Xが続きます。結果として、SIとREAD UNCOMMITTEDは、RCSIが有効になっていない場合のUPDLOCK、TABLOCKシナリオでこのデッドロックが発生しにくい唯一の分離レベルです。
デッドロックの修正
この修正により、UPDLOCKとTABLOCKが一緒に指定された場合に、すべての分離レベルで取得されるロックが変更されます。 、および関係なく RCSIが有効かどうかの修正が適用された後、UPDLOCKとTABLOCKにより、エンジンはTab-SIX(インテント排他で共有されるテーブルレベル)を取得し、Tab-Xに変換されます。
これにより、Tab-SIXは別のTab-SIXと互換性がないため、デッドロックシナリオが回避されます。デッドロックは、2つのプロセスがTab-IXをTab-Xへの変換を待機しているときに発生したことを忘れないでください。 Tab-IXがTab-SIXに置き換えられたため、両方がTab-SIXを同時に保持することはできません。その結果、デッドロックではなく通常のブロッキングシナリオが発生します。
最終的な考え
「誤ったデッドロック」修正は、特定のデッドロックシナリオを解決しますが、それでも、UPDLOCKとTABLOCKを指定する人々が想定する動作にはなりません。 SQL ServerにTab-U(テーブルレベルの更新)ロックがあった場合、テーブルへの同時変更は防止されますが、同時リーダーは許可されます。これは、これらのヒントを一緒に使用する人々の意図がどのようになるかを私が想像するものであり、それがどのように役立つかを見ることができます。
現在の実装(欠落しているTab-Uの代わりにTab-Xが最終的に使用される)は、Tab-Xが同時読み取りを防止するため(行バージョン管理の分離レベルが使用されていない場合)、この期待に一致しません。多くの場合、TABLOCKXを指定した方がよいでしょう。修正によって新しいバグ(SQL Server 2008 R2のユーザーのみ)も導入されるという事実も残念です。特に、バグが2008R2SP3に含まれる場合はなおさらです。
デッドロックの修正は、2008R2より前のバージョンのSQLServerでは利用できないことに注意してください。これらのバージョンでは、上記のように、UPDLOCKとTABLOCKの複雑なロック動作が引き続き発生します。
RCSIの下でのデータ変更に関する私の記事へのコメントでこの問題に最初に注意を向けてくれたEugeneKarpovichに感謝します。