web-dev-qa-db-ja.com

SQL Serverインデックス更新デッドロック

2つのクエリがあり、同時に実行するとデッドロックが発生します。

クエリ1-インデックス(index1)に含まれる列を更新します。

update table1 set column1 = value1 where id = @Id

Table1でXロックを取得してから、index1でXロックを試行します。

クエリ2:

select columnx, columny, etc from table1 where {some condition}

Index1でSロックを取得し、次にtable1でSロックを試行します。

同じクエリを維持しながらデッドロックを防ぐ方法はありますか?たとえば、テーブルとインデックスのアクセスが同じ順序であることを確認するために、更新前に更新トランザクションのインデックスでXロックをどうにかして行うことができますか?これにより、デッドロックを防ぐことができますか?

分離レベルはRead Committedです。行とページのロックがインデックスに対して有効になっています。同じレコードが両方のクエリに参加している可能性があります。パラメーターが表示されないため、デッドロックグラフからはわかりません。

デッドロックグラフ

13
Dale K

同じクエリを維持しながらデッドロックを防ぐ方法はありますか?

デッドロックグラフは、この特定のデッドロックが、ブックマークルックアップ(この場合はRIDルックアップ)に関連付けられた変換デッドロックであることを示しています。

Deadlock graph

質問が指摘しているように、クエリが同じリソースに対して異なる順序で互換性のないロックを取得する可能性があるため、一般的なデッドロックリスクが発生します。 RIDルックアップのため、SELECTクエリはテーブルの前にインデックスにアクセスする必要がありますが、UPDATEクエリは最初にテーブルを変更し、次にインデックスを変更します。

デッドロックを解消するには、デッドロックの成分の1つを取り除く必要があります。主なオプションは次のとおりです。

  1. 非クラスター化インデックスをカバーすることにより、RIDルックアップを回避します。 SELECTクエリは26列を返すため、これはおそらく実用的ではありません。
  2. クラスタ化インデックスを作成して、RIDルックアップを回避します。これには、列Proposalにクラスター化インデックスを作成する必要があります。これは検討に値しますが、この列はuniqueidentifierタイプであると思われます。これは、より広範な問題に応じて、クラスター化インデックスに適している場合とそうでない場合があります。
  3. READ_COMMITTED_SNAPSHOTまたはSNAPSHOTデータベースオプションを有効にして、読み取り時に共有ロックを取得しないようにします。これには、特に設計されたブロッキング動作に関して、慎重なテストが必要になります。トリガーコードには、ロジックが正しく実行されることを確認するためのテストも必要です。
  4. SELECTクエリにREAD UNCOMMITTED分離レベルを使用して、読み取り時に共有ロックを取得しないでください。通常の警告がすべて適用されます。
  5. 排他的なアプリケーションロックを使用して、問題の2つのクエリの同時実行を回避します( sp_getapplock を参照)。
  6. テーブルロックヒントを使用して、同時実行性を回避します。これは、質問で特定された2つだけでなく、他のクエリに影響を与える可能性があるため、オプション5よりも大きなハンマーです。

テーブルとインデックスのアクセスが同じ順序であることを確認するために、更新前に更新トランザクションのインデックスで何らかの方法でXロックを取得できますか

これを試すには、明示的なトランザクションで更新をラップし、更新前に非クラスター化インデックス値に対してSELECTヒントを使用してXLOCKを実行します。これは、非クラスター化インデックスの現在の値が何であるかを確実に把握し、実行計画を正しく立て、この追加のロックを取得することによるすべての副作用を正しく予測することに依存しています。 冗長であると判断された場合 の場合、ロックを回避するのに十分なほど賢くないロックエンジンにも依存します。

つまり、これは基本的には実現可能ですが、お勧めしません。何かを見逃したり、独創的な方法で自分を出し抜くのは簡単です。 (単に検出して再試行するのではなく)これらのデッドロックを本当に回避する必要がある場合は、代わりに上記のより一般的な解決策を検討することをお勧めします。

11
Paul White 9

私は時々発生する同様の問題があり、ここに私が取るアプローチがあります。

  1. 追加 set deadlock priority low;を選択します。これにより、デッドロックが発生したときに、このクエリがデッドロックの犠牲になります。
  2. アプリケーション内で再試行ロジックを設定して、ブロッククエリが完了するまでしばらく待機/スリープした後、デッドロック(またはタイムアウト)により失敗した場合に選択を自動的に再試行します。

注:selectが明示的なマルチステートメントトランザクションの一部である場合、失敗したステートメントだけでなく、トランザクション全体を再試行する必要があります。そうしないと、予期しない結果が発生する可能性があります。これが単一のselectの場合は問題ありませんが、トランザクション内のxのステートメントnの場合は、再試行中にすべてのnステートメントを必ず再試行してください。

1
BateTech