MSSQL2012。ストアドプロシージャが互いに10ミリ秒以内に同じパラメーターで2回起動し、デッドロックが発生します。
ストアドプロシージャは基本的に次のようになります(実際のコードを共有することはできませんが、関連する疑似コードを示します)。
BEGIN TRAN
UPDATE t
SET
t.Column1 = NewValue1,
t.Column2 = NewValue2,
t.Column3 = NewValue3,
etc for 20 columns...
FROM Table t
INNER JOIN Results r
ON r.ID = t.ResultID
WHERE
UserID = @userid
AND Deleted = 0
UPDATE t
SET Deleted = 1
FROM Table t
LEFT JOIN Results r
ON r.ID = t.ResultID
WHERE
t.UserID = @userid
AND t.Deleted = 0
AND r.ID IS NULL
COMMIT TRAN
テーブルでデッドロックが発生し、関連するインデックスはUserID(非クラスター化カバリングインデックス)と主キーです。テスト環境でプロシージャを実行すると、実行プランは、両方のUPDATEがカバリングインデックスを使用して行を取得し(シーク、ルックアップなし)、PKとカバリングインデックスを更新する必要があることを示しています。
私が考えた可能な解決策は次のとおりです:
私はdbaとしてのキャリアから約2か月です。デッドロックは頭を動かすのが困難でした。どんなアドバイスでも大歓迎です!
2つのupdate
ステートメントはそれぞれ、明示的または暗黙的に関わらず、異なるトランザクション内で実行するとデッドロックを引き起こす可能性があります。どちらも同じ行のセットを変更します。行がロックされる順序は保証されません。
1つのオプションは、エラーをキャッチして、失敗したトランザクションを再実行することです。別のアプローチは、更新を何らかの方法で「シリアル化」することです。
私の意見では、最も簡単で最も速い方法は、プロシージャを変更して、同じプロシージャの他のインスタンスが更新を完了するまで待機することです。これは、たとえば sp_getapplock を使用して実装できます。
別のオプションは、行が同じ順序でロックされるようにすることです(たとえば、-openカーソルが特定の(一意の)順序で必要なすべての行を読み取り、それを反復し、各行の問題についてSELECT WITH ROWLOCK
)、すべての行がロックされた後にupdate
を発行します。次に、同時実行の場合、2番目のトランザクションは最初のトランザクションを待ちます。 SQLServerの場合、ロックのエスカレーションの可能性があるため、デッドロックが発生する可能性はほとんどないと思います。
2つのSQLを1に結合しないのはなぜですか。
BEGIN TRAN
UPDATE t
SET
t.Column1 = case when r.ID is null and Deleted = 0 then NewValue1 else t.Column1 end,
...
Deleted = case when r.ID is null then 1 else 0 end
FROM Table t
left outer join Results r ON r.ID = t.ResultID
WHERE
UserID = @userid
COMMIT TRAN
2つのプロセスが同じテーブルにアクセスするため、デッドロックが発生する可能性がありますが、それぞれが反対側からアクセスし、追加のロックが発生します。コードを見ると、2つの更新がデッドロックの根本的な原因であるとは思いませんが、バックグラウンドで実行されている他のステートメントの犠牲者が多くなっています。
データベース上の他のすべてのアクティビティを停止して、2つの更新手順のみを同時に実行できる場合でも、デッドロックは発生しますか?
デッドロックはここで説明されています: デッドロックの検出と終了
記事の情報に従ってデッドロックを監視できます。 SQL Server 2008以降で拡張イベントを使用してデッドロックを監視する方法
2つのトレースフラグ をオンにして、SQL Serverのエラーログファイルにデッドロックを記録することで、迅速な勝利を得ることができます。
デッドロック情報があれば、実際の根本原因を特定できます。
余談ですが、実際には1つのステートメントがロールバックされるデッドロックがありますか?または、複数のspidが1つのspidが完了するのを待っているブロッキングチェーンがありますか? (SQL Serverエラーログで常にデッドロックを確認できるはずです)。
デッドロック!=ブロッキングチェーン
どちらの更新ステートメントも、明らかに単一の事業単位ではありません。最初の更新ステートメントは、結果テーブルからの一致するレコードに基づいてテーブルを更新することに関するものであり、2番目の更新ステートメントは、一致しないレコードに対するものです。
したがって、明示的なトランザクションを削除することは、私の意見では明らかな選択に見えます。
デッドロックを回避する方法はいくつかありますが、私はそこに努力をかける気はありません。