web-dev-qa-db-ja.com

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

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

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

後で、データベースのスクリプトを作成して開発を混乱させる開発者を見つけました。主キーを追加し、問題は解決しました。

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

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

私はそれが明確な結論ではないことを知っています、それが私がここに投稿している理由です...

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

デッドロックの追跡は、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はBをロックしてからAが処理時間を取り戻すことができます。これでデッドロックが発生しました。

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

コードブロック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の間のデッドロックが防止されます。

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

39
BlackICE

デッドロックを読んで学ぶための私のお気に入りの記事は次のとおりです シンプルトーク-デッドロックの追跡 および SQL Server Central-プロファイラーを使用したデッドロックの解決 。彼らはあなたにサック状況を処理する方法についてのサンプルとアドバイスを提供します。

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

しかし、記事をよく読むと、アドバイスがはるかに良くなります。

24
Marian

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

たとえば、 InnoDB の場合:

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

もう1つの一般的な解決策は、不要なときにトランザクションの一貫性をオフにするか、そうでなければ 分離レベル を変更することです。たとえば、統計を計算する長期実行ジョブ...近い答えで通常は十分です。彼らはあなたの下から変化しているので、正確な数は必要ありません。また、完了までに30分かかる場合、それらのテーブルの他のすべてのトランザクションを停止する必要はありません。

...

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

16
Joe

カーソルで開発するだけです。本当に悪いです。テーブル全体をロックしてから、行を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
7

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

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

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

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

6
Gerard ONeill