デッドロックの主な原因は何ですか?それを防ぐことができますか?


55

最近、ASP.NETアプリケーションの1つがデータベースデッドロックエラーを表示し、エラーをチェックして修正するように要求されました。デッドロックの原因は、カーソル内のテーブルを厳密に更新しているストアドプロシージャであることがわかりました。

このエラーを見たのはこれが初めてで、効果的に追跡して修正する方法がわかりませんでした。私が知っているすべての可能な方法を試してみましたが、最終的に、更新されているテーブルには主キーがないことがわかりました!幸いなことに、これはID列でした。

後で、展開用のデータベースを台無しにした開発者を見つけました。主キーを追加し、問題は解決しました。

私は幸せを感じて、私のプロジェクトに戻って、そのデッドロックの理由を見つけるためにいくつかの研究をしました...

どうやら、デッドロックを引き起こしたのは循環待機状態だったようです。更新は、主キーの場合よりも主キーなしの方が明らかに時間がかかります。

明確に定義された結論ではないことを知っているので、ここに投稿しています...

  • 主キーの欠落が問題ですか?
  • (相互排除、保留と待機、プリエンプションなし、循環待機)以外のデッドロックを引き起こす他の条件はありますか?
  • デッドロックを防止および追跡するにはどうすればよいですか?

2
私が見たデッドロックの大部分(すべて?)は、循環待機(主にトリガーの過度な使用が原因)が原因で発生しています。
サティアジスバート

循環性は、デッドロックの必要条件の1つです。すべてのセッションが同じ順序でロックを取得する場合、デッドロックを回避できます。
ピーターG.

回答:


38

デッドロックの追跡は、次の2つの方が簡単です。

デフォルトでは、デッドロックはエラーログに書き込まれません。SQLで、トレースフラグ1204および3605を使用してエラーログにデッドロックを書き込むことができます。

SQL Serverエラーログにデッドロック情報を書き込みます:DBCC TRACEON(-1、1204、3605)

無効にする:DBCC TRACEOFF(-1、1204、3605)

トレースフラグ1204と、オンになったときに得られる出力については、「デッドロックのトラブルシューティング」を参照してください。 https://msdn.microsoft.com/en-us/library/ms178104.aspx

予防はより困難であり、基本的に次のことに注意する必要があります。

コードブロック1は、リソースAをロックし、次にリソースBをこの順序でロックします。

コードブロック2は、リソースB、次にリソースAをこの順序でロックします。

これはデッドロックが発生する古典的な状態です。両方のリソースのロックがアトミックではない場合、コードブロック1はAをロックしてプリエンプトすることができ、コードブロック2はAが処理時間を戻す前にBをロックします。デッドロックが発生しました。

この状態を防ぐために、次のようなことができます

コードブロックA(擬似コード)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

コードブロックB(擬似コード)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

AとBのロック解除を忘れないでください

これにより、コードブロックAとコードブロックB間のデッドロックが防止されます。

データベースの観点から、ロックはデータベース自体、つまりデータ更新時の行/テーブルロックによって処理されるため、この状況を防ぐ方法についてはわかりません。私が最も多くの問題が発生するのを見たのは、カーソル内であなたが見た場所です。カーソルは非常に非効率的であるため、可能な限り避けてください。


コードブロックBでリソースBの前にリソースAをロックするつもりでしたか?書かれているように、これはデッドロックを引き起こします..あなた自身が以前のコメントで言及したように。ロックの順序を確保するために最初にダミークエリが必要な場合でも、可能な限り、常に同じ順序でリソースをロックする必要があります。
ジェラルド・オニール

23

デッドロックについて読んで学ぶための私のお気に入りの記事は次のとおりです。SimpleTalk-デッドロックの 追跡SQL Server Central-Profilerを使用したデッドロックの解決。彼らはあなたにサンプルを提供し、状況を吸う方法についてのアドバイスを提供します。

つまり、現在の問題を解決するために、関連するトランザクションを短くし、不要な部分を取り除き、オブジェクトの使用順序を処理し、実際に必要な分離レベルを確認し、不要なものを読み取らないようにしますデータ...

しかし、記事をよく読んでください、彼らはアドバイスではるかに良いでしょう。


16

デッドロックは、データベースがテーブル全体ではなく個々のレコードをロックできるようにするため、インデックスを追加することで解決できる場合があります。これにより、競合や物事が詰まる可能性を減らします。

たとえば、InnoDBの場合

ステートメントに適したインデックスがなく、MySQLがテーブル全体をスキャンしてステートメントを処理する必要がある場合、テーブルのすべての行がロックされ、他のユーザーによるテーブルへのすべての挿入がブロックされます。クエリが不必要に多くの行をスキャンしないように、適切なインデックスを作成することが重要です。

別の一般的な解決策は、不要な場合にトランザクションの一貫性をオフにするか、分離レベルを変更することです。たとえば、統計を計算するための長時間実行ジョブ...彼らはあなたの下から変化しているように。完了までに30分かかる場合、それらのテーブル上の他のすべてのトランザクションを停止することは望ましくありません。

...

それらの追跡については、使用しているデータベースソフトウェアによって異なります。


ダウン投票の際にコメントを提供するのは一般的な礼儀です...これは有効な答えです。テーブルロックにアップグレードして永久に取得するselectステートメントは確実にデッドロックを引き起こす可能性があります。
BlackICE

1
インデックスがクラスタ化されていない場合、MS SQLServerは予期しないロック動作を引き起こす可能性もあります。行レベルのロックを使用するという指示を静かに無視し、ページレベルのロックを実行します。その後、ページでデッドロックを待機させることができます。
ジェイ

7

カーソルの事を開発するだけです。本当に悪いです。テーブル全体をロックしてから、行を1つずつ処理します。

whileループを使用してカーソルのように行を移動するのが最善です

whileループでは、ループ内の各行に対して選択が実行され、ロックは一度に1行のみで発生します。テーブル内の残りのデータはクエリのために無料であるため、デッドロックが発生する可能性が低くなります。

さらに、高速です。とにかくカーソルがあるのか​​不思議に思うでしょう。

この種の構造の例を次に示します。

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

IDフィールドがスパースの場合、IDの個別のリストを取得し、それを反復処理することができます。

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END

6

主キーがなくても問題はありません。少なくともそれだけで。まず、インデックスを作成するためにプライマリは必要ありません。次に、テーブルスキャンを実行している場合でも(特定のクエリがインデックスを使用していない場合に発生する必要がありますが、テーブルロック自体はデッドロックを引き起こしません。書き込みプロセスは読み取りを待機し、読み取りプロセスは書き込みを待ちます。もちろん、読み取りは互いに待つ必要はありません。

他の答えに加えて、トランザクションの分離レベルが重要です。なぜなら、反復可能な読み取りとシリアル化は、トランザクションの終了まで「読み取り」ロックを保持するためです。リソースをロックしても、デッドロックは発生しません。それをロックしたままにします。書き込み操作では、トランザクションが終了するまで常にリソースがロックされます。

私のお気に入りのロック防止戦略は、「スナップショット」機能を使用することです。コミットされたスナップショットの読み取り機能は、読み取りがロックを使用しないことを意味します!また、「コミットの読み取り」よりも詳細な制御が必要な場合は、「スナップショット分離レベル」機能があります。これにより、他のプレイヤーをブロックせずに、シリアル化された(ここではMS用語を使用して)トランザクションを実行できます。

最後に、更新ロックを使用すると、デッドロックの1つのクラスを防ぐことができます。読み取り(HOLD、または反復可能読み取りを使用)を読み取り、保持し、別のプロセスが同じことを行った場合、両方が同じレコードを更新しようとすると、デッドロックが発生します。ただし、両方が更新ロックを要求した場合、2番目のプロセスは最初のプロセスを待機し、他のプロセスはデータが実際に書き込まれるまで共有ロックを使用してデータを読み取ることができます。もちろん、プロセスの1つが共有HOLDロックを要求している場合、これは機能しません。


-2

SQL Serverのカーソルは低速ですが、カーソルのソースデータを一時テーブルにプルし、カーソルを実行することで、カーソルのデッドロックを回避できます。これにより、カーソルが実際のデータテーブルをロックすることを防ぎます。取得するロックは、カーソル内で実行される更新または挿入に対してのみであり、カーソルの継続時間ではなく、挿入/更新の間のみ保持されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.