web-dev-qa-db-ja.com

1つのテーブルでの2つの選択と1つの更新の間のSQL Serverデッドロック

私たちのアプリケーションは時々(週に1回程度)デッドロックに遭遇します。

主な原因は、2つの選択を持つクエリのようです。 1つはパフォーマンス上の理由から一時テーブルを埋めることであり、もう1つは多くの詳細を含むすべての予定のリストを返すための多くの結合を持つ比較的複雑な選択です。 2番目の選択について私が見る唯一の特別な可能性は、自己結合が含まれていることです。 2つの選択クエリは常に、SQL Serverによるデッドロックイベントレポートの一部です。

他のクエリは、同じテーブルに対する単純なDMLクエリ(挿入または更新)ですが、これは常に同じDMLクエリであるとは限りません。どちらのクエリも、明示的なトランザクション内ではなく、標準のREAD COMMITTED分離で実行されます。

2つのクエリはおおよそ次のとおりです(説明のために短縮しました)。

DECLARE @futureAppointments TABLE(clientId int, StartDate date)
INSERT INTO @futureAppointments SELECT clientId, StartDate FROM Appointments where StartDate >= @startDate

SELECT *, (SELECT COUNT(*) FROM @futureAppointments fa WHERE fa.clientId = a.clientId AND fa.StartDate > a.StartDate)
FROM Appointments a
join b on a.fk_b = b.id
join c on a.fk_c = c.id
join Appointments d on c.somefield = d.anotherfield
WHERE a.StartDate >= @startDate AND a.StartDate <= @endDate
UPDATE Appointments SET someField = @value WHERE id = @id

例2: deadlock2.xmldeadlock graph for Exmaple2 例3: deadlock3.xmldeadlock graph for Example3

このシナリオでデッドロックが発生しないようにするにはどうすればよいですか?また、例3のように、2つのselectを含む最初のステートメントが、選択されたテーブルのPKでUロックを取得する理由を誰かが知っていますか?それは重要だとは思いませんが、奇妙に思えます。

5
final

デッドロックグラフを見ると:

enter image description here

クエリの更新-SPID 75

更新クエリのロック(spid 75)はかなり簡単です、[〜#〜] x [〜#〜](排他的)ロックがキー/行の値に対して要求されていますが、その行は現在選択クエリによってロックされています。

更新クエリは、[〜#〜] ix [〜#〜](意図的排他)ロックも保持します。ページに属する行の排他ロック。 [〜#〜] ix [〜#〜]ロックは[〜#〜]と互換性があります[〜#〜](インテント共有)選択クエリによって発行されたロック(の結果として)[〜#〜] s [〜#〜] 行をロックします)。

参照: ロック互換性マトリックス


クエリを選択-SPID 103

デッドロックの異常な部分は、選択クエリ(spid 103)[〜#〜] s [〜#〜](共有)行と、行データ(およびその他の行データ)があるページの両方をロックします。特に、行からページへのロックのエスカレーションは不可能です(行からテーブルおよびページからテーブル)。ロックを保持している以前のトランザクションも除外されます。

説明は、_dbo.Appointments_テーブルへの二重アクセスにあるようです。ロックは2回行われ、これらのテーブルアクセスの1つはページロックを必要としますが、もう1つはすでに行ロックを取得しています。

取得されるこれらの共有ロックの間に更新が発生します。

要求された/取得されたロックの例

  1. A[〜#〜] s [〜#〜]キー/行のロックは、dbo.Appointmentsテーブルの最初の読み取りアクセスによって行われます。選択クエリの一部
  2. [〜#〜] ix [〜#〜]ロックは、更新クエリによって行データがあるページで取得されます
  3. [〜#〜] x [〜#〜]更新クエリによって行でロックが要求されます
  4. 行データも持っているページの[〜#〜] s [〜#〜]ロックは、dboへの2番目のアクセスによって要求されます。選択クエリの一部として

これはすべて、選択クエリのこの部分がデッドロックの問題であることを意味します。

_SELECT *, (SELECT COUNT(*) FROM @futureAppointments fa WHERE fa.clientId = a.clientId AND fa.StartDate > a.StartDate)
FROM Appointments a
join b on a.fk_b = b.id
join c on a.fk_c = c.id
join Appointments d on c.somefield = d.anotherfield
WHERE a.StartDate >= @startDate AND a.StartDate <= @endDate;
_

このシナリオでデッドロックが発生しないようにするにはどうすればよいですか?

Reduce

デッドロックが発生する可能性を減らすには、インデックスを書き換えるか追加して、選択クエリを最適化することを検討します。

削除

これら2つのクエリ間のデッドロックの可能性を完全に排除するには、一時テーブルを使用して自己結合を2つの部分に分割するか、WTIH(PAGLOCK)/WITH(TABLOCK)。これは同時実行性に影響を与えることに注意してください。

また、例3のように、2つのselectを含む最初のステートメントが、選択されたテーブルのPKでUロックを取得する理由を誰かが知っていますか?それは重要だとは思いませんが、奇妙に思えます。

所有者(選択クエリ)は、キーの共有ロックを保持しています。

[〜#〜] u [〜#〜](更新)ロック(更新クエリから来る)は共有ロックと互換性があります:- ロック互換性マトリックス

更新クエリは、Uロックを排他ロックに変換しようとします。

_<waiter id="process24b7826bc28" mode="X" requestType="convert" />_。

SentryOne Plan Explorer (無料ツール)を使用してXMLを開くと、情報は正しいです。

7
Randi Vertongen