web-dev-qa-db-ja.com

ストアドプロシージャ自体がデッドロックしていますが、どうすれば解決できますか?

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とカバリングインデックスを更新する必要があることを示しています。

私が考えた可能な解決策は次のとおりです:

  1. 明示的なトランザクション制御を削除します。 2つの更新は実際には相互に依存していないため、実行可能であるように見えます。デッドロックは解決しますか?
  2. PKを削除し、UserIDを(一意でない)クラスター化インデックスにします。ただし、これは、他の場所でPKを使用する他のものに影響を与える可能性があります。
  3. 2番目のUPDATEにいくつかのブールロジックを実装して、不必要に実行されないようにします。ただし、実際に問題を修正するのではなく、問題の発生を少なくします。

私はdbaとしてのキャリアから約2か月です。デッドロックは頭を動かすのが困難でした。どんなアドバイスでも大歓迎です!

3
Zaphodb2002

2つのupdateステートメントはそれぞれ、明示的または暗黙的に関わらず、異なるトランザクション内で実行するとデッドロックを引き起こす可能性があります。どちらも同じ行のセットを変更します。行がロックされる順序は保証されません。
1つのオプションは、エラーをキャッチして、失敗したトランザクションを再実行することです。別のアプローチは、更新を何らかの方法で「シリアル化」することです。

私の意見では、最も簡単で最も速い方法は、プロシージャを変更して、同じプロシージャの他のインスタンスが更新を完了するまで待機することです。これは、たとえば sp_getapplock を使用して実装できます。

別のオプションは、行が同じ順序でロックされるようにすることです(たとえば、-openカーソルが特定の(一意の)順序で必要なすべての行を読み取り、それを反復し、各行の問題についてSELECT WITH ROWLOCK)、すべての行がロックされた後にupdateを発行します。次に、同時実行の場合、2番目のトランザクションは最初のトランザクションを待ちます。 SQLServerの場合、ロックのエスカレーションの可能性があるため、デッドロックが発生する可能性はほとんどないと思います。

2
a1ex07

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
Ashwin Kambli

2つのプロセスが同じテーブルにアクセスするため、デッドロックが発生する可能性がありますが、それぞれが反対側からアクセスし、追加のロックが発生します。コードを見ると、2つの更新がデッドロックの根本的な原因であるとは思いませんが、バックグラウンドで実行されている他のステートメントの犠牲者が多くなっています。

データベース上の他のすべてのアクティビティを停止して、2つの更新手順のみを同時に実行できる場合でも、デッドロックは発生しますか?

デッドロックはここで説明されています: デッドロックの検出と終了

記事の情報に従ってデッドロックを監視できます。 SQL Server 2008以降で拡張イベントを使用してデッドロックを監視する方法

2つのトレースフラグ をオンにして、SQL Serverのエラーログファイルにデッドロックを記録することで、迅速な勝利を得ることができます。

デッドロック情報があれば、実際の根本原因を特定できます。

余談ですが、実際には1つのステートメントがロールバックされるデッドロックがありますか?または、複数のspidが1つのspidが完了するのを待っているブロッキングチェーンがありますか? (SQL Serverエラーログで常にデッドロックを確認できるはずです)。

デッドロック!=ブロッキングチェーン

0

どちらの更新ステートメントも、明らかに単一の事業単位ではありません。最初の更新ステートメントは、結果テーブルからの一致するレコードに基づいてテーブルを更新することに関するものであり、2番目の更新ステートメントは、一致しないレコードに対するものです。

したがって、明示的なトランザクションを削除することは、私の意見では明らかな選択に見えます。

デッドロックを回避する方法はいくつかありますが、私はそこに努力をかける気はありません。

0
Ashwini Mohan