4

互いにデッドロックしている 2 つの SQL ステートメントを説明する次のデッドロック グラフがあります。この問題を分析し、SQL コードを修正してこれが起こらないようにする方法がわかりません。

主なデッドロック グラフ

代替テキスト http://img140.imageshack.us/img140/6193/deadlock1.png クリックすると大きな画像が表示されます。

左側、詳細

代替テキスト http://img715.imageshack.us/img715/3999/deadlock2.png クリックすると大きな画像が表示されます。

右側、詳細

代替テキスト http://img686.imageshack.us/img686/5097/deadlock3.png クリックすると大きな画像が表示されます。

生のデッドロック スキーマ xml ファイル

ここをクリックして xml ファイルをダウンロードします

テーブル スキーマ

代替テキスト http://img509.imageshack.us/img509/5843/deadlockschema.png

LogEntries テーブルの詳細

代替テキスト http://img28.imageshack.us/img28/9732/deadlocklogentriestable.png

接続済みクライアント テーブルの詳細

代替テキスト http://img11.imageshack.us/img11/7681/deadlockconnectedclient.png

コードは何をしていますか?

同時にいくつかのファイル (たとえば、この例では 3 つとしましょう) を読み込んでいます。各ファイルには異なるデータが含まれていますが、同じタイプのデータが含まれています。次に、データをLogEntriesテーブルに挿入し、(必要に応じて)ConnectedClientsテーブルに何かを挿入または削除します。

これが私のSQLコードです。

using (TransactionScope transactionScope = new TransactionScope())
{
    _logEntryRepository.InsertOrUpdate(logEntry);

    // Now, if this log entry was a NewConnection or an LostConnection, then we need to make sure we update the ConnectedClients.
    if (logEntry.EventType == EventType.NewConnection)
    {
        _connectedClientRepository.Insert(new ConnectedClient { LogEntryId = logEntry.LogEntryId });
     }

    // A (PB) BanKick does _NOT_ register a lost connection .. so we need to make sure we handle those scenario's as a LostConnection.
    if (logEntry.EventType == EventType.LostConnection ||
        logEntry.EventType == EventType.BanKick)
    {
        _connectedClientRepository.Delete(logEntry.ClientName, logEntry.ClientIpAndPort);
    }

    _unitOfWork.Commit();
    transactionScope.Complete();
}

現在、各ファイルには独自のUnitOfWorkインスタンスがあります (つまり、独自のデータベース接続、トランザクション、およびリポジトリ コンテキストがあります)。したがって、これは、データベースへの3つの異なる接続がすべて同時に発生していることを意味すると想定しています。

最後に、これはEntity Frameworkリポジトリとして使用していますが、この問題について考えるのをやめさせないでください

プロファイリング ツールを使用するとIsolation LevelSerializable. ReadCommitedとも試しましReadUncommitedたが、どちらもエラーです:-

  • ReadCommited: 同上。デッドロック。
  • ReadUncommited: 別のエラー。なんらかの結果が返されることを期待していたが、何も得られなかったという EF 例外。LogEntryId これは期待されるIdentity ( ) 値であると推測してscope_identityいますが、ダーティ リードのために取得されません。

助けてください!

PS。ところで、それはSql Server 2008です。


アップデート #2

Remus Rusanuの更新された返信を読んだ後、他の誰かがさらに助けてくれるかどうかを確認するために、もう少し情報を提供できると感じました.

EFダイアグラム

代替テキスト http://img691.imageshack.us/img691/600/deadlockefmodel.png

さて、Remus は提案します (そして、彼は EF に慣れていないと言っています)...

パズルの最後のピースである、左ノードが PK_ConnectedClients に持っている原因不明のロックは、InsertOrUpdate の EF 実装によるものだと思います。おそらく最初にルックアップを行い、ConnectedClients と LogEntries の間で宣言された FK 関係のために、PK_ConnectedClients をシークし、シリアライズ可能なロックを取得します。

面白い。上記のように、左側のノードがロックされている理由がわかりませんPK_ConnectedClients。わかりました、そのメソッドのコードをチェックしてみましょう....

public void InsertOrUpdate(LogEntry logEntry)
{
    LoggingService.Debug("About to InsertOrUpdate a logEntry");

    logEntry.ThrowIfArgumentIsNull("logEntry");

    if (logEntry.LogEntryId <= 0)
    {
        LoggingService.Debug("Current logEntry instance doesn't have an Id. Instance object will be 'AddObject'.");
        Context.LogEntries.AddObject(logEntry);
    }
    else
    {
        LoggingService.Debug("Current logEntry instance has an Id. Instance object will be 'Attached'.");
        Context.LogEntries.Attach(logEntry);
    }
}

うーん。それは単純なAddObject(別名挿入)またはAttach(別名更新)です。参照なし。Sql コードは、ルックアップのヒントもありません。

わかりました...他に2つの方法があります...おそらくそれらはいくつかのルックアップを行っていますか?

ConnectedClientRepository で ...

public void Insert(ConnectedClient connectedClient)
{
    connectedClient.ThrowIfArgumentIsNull("connectedClient");

    Context.ConnectedClients.AddObject(connectedClient);
}

いいえ -> また、基本的なように。

ラッキーラストメソッド?うわー..これは面白いです....

public void Delete(string clientName, string clientIpAndPort)
{
    clientName.ThrowIfArgumentIsNullOrEmpty("clientName");
    clientIpAndPort.ThrowIfArgumentIsNullOrEmpty("clientIpAndPort");

    // First we need to attach this object to the object manager.
    var existingConnectedClient = (from x in GetConnectedClients()
                                   where x.LogEntry.ClientName == clientName.Trim() &&
                                   x.LogEntry.ClientIpAndPort == clientIpAndPort.Trim() &&
                                   x.LogEntry.EventTypeId == (byte)EventType.NewConnection
                                   select x)
                                  .Take(1)
                                  .SingleOrDefault();

    if (existingConnectedClient != null)
    {
        Context.ConnectedClients.DeleteObject(existingConnectedClient);
    }
}

したがって、上記を見て、削除したいレコードのインスタンスを取得し、存在する場合は削除します。

だから..そのメソッド呼び出しをコメントアウトすると、最初のロジックの方法でこのSO投稿の一番上まで...どうなりますか?

できます。うわー。

また、どちらかSerializableまたはどちらでも機能します-メソッドRead Commitedを呼び出さない場合は両方とも機能します。Delete

では、なぜその削除メソッドがロックされるのでしょうか? select ( with serializable) がロックし、デッドロックが発生するためですか?

ではread committed、削除への呼び出しが同時に 3 回発生する可能性はありますか。

  • 1 つ目は、データのインスタンスを取得します。
  • 2 番目 (および 3 番目) は、同じデータの別のインスタンスを取得します。
  • さて、1回目の削除です。大丈夫。
  • 2番目の削除..しかし、行がなくなった..したがって、予期しない行数(0)に影響を与えるという奇妙なエラーが発生します。<== 0 個のアイテムが削除されました。

可能?もしそうなら..ええと...どうすればこれを修正できますか? これは競合状態の典型的なケースですか? どうにかしてこれを防ぐことはできますか?


アップデート

  • 画像へのリンクを修正しました。
  • 生の XML デッドロック ファイルへのリンク。ここに同じリンクがあります。
  • データベース テーブル スキーマが追加されました。
  • 両方の表の詳細を追加しました。
4

1 に答える 1

4

左側のノードはRangeS-U lockオンPK_CustomerRecordsを保持していて、RangeS-Uロックを必要としていますi1(インデックスがオンであると想定していますLogEntries)。右側のノードにはRangeS-Uロックがi1あり、RangeI-Nオンが必要PK_CustomerRecordsです。

どうやら、デッドロックは_logEntriesRepository.InsertOrUpdate(左ノード)と_connectedClientRepository.Insert(右ノード)の間で発生します。宣言されたEF関係のタイプを知らなければ、左側のノードがをPK_CustomerRecords挿入したときにロックがオンになっている理由についてコメントすることはできませんLogEntry。これは、「プリロードされた」メンバーのルックアップなど、EFによって引き起こされたORMタイプの動作が原因であるか、投稿されたコードのスコープを囲む高レベルのTransactionScopeが原因である可能性があります。

他の人が言ったように、アクセスパス(使用されるインデックス)が重要であるため、デッドロック評価でデータベーススキーマを投稿する必要があります。デッドロックにおけるインデックスの影響の詳細については、私の記事「読み取り/書き込みデッドロック」を参照してください。

私の最初の推奨事項は、トランザクションスコープを強制的にすることread committedです。TransactionScopesのデフォルトのシリアル化可能なレベルは、実際にはほとんど必要ありません。パフォーマンスが低下します。この特定のケースでは、範囲ロックを方程式に取り入れることで、デッドロックの調査に多くの不要なノイズが発生し、すべてが複雑になります。readcommitedで発生するデッドロック情報を投稿してください。

また、デッドロックグラフの写真を投稿しないでください。写真は、ここでは千の言葉が真実ではないことを示しています。元のデッドロックXMLを投稿してください。きれいな写真には表示されない情報がたくさんあります。

アップデート

デッドロックXMLから、左側のノードが実行されていることがわかりますinsert [dbo].[LogEntries]([GameFileId], [CreatedOn], [EventTypeId], [Message], [Code], [Violation], [ClientName], [ClientGuid], [ClientIpAndPort]) values (@0, @1, @2, null, null, null, @3, @4, @5)<executionStack><frame>要素)。しかし、もっと重要なのは、不思議なインデックス「i1」の背後にあるオブジェクトを見ることができるということですobjectname="AWing.sys.fulltext_index_docidstatus_1755869322" indexname="i1"。したがって、デッドロックはフルテキストインデックスで発生します。

したがって、デッドロックの完全な説明は次のとおりです。

  • 右側のノードは_connectedClientRepository.Insertにあり、PK_ConnectedClientsの範囲挿入ロックが必要です。以前に_logEntryRepository.InsertOrUpdateを実行していたフルテキストインデックスi1に対してRangeS-Uロックがあります。
  • 左側のノードは、バッチ内のINSERTステートメントの_logEntryRepository.InsertOrUpdateにあり、フルテキストインデックスi1のRangeS-Uロックが必要です。PK_ConnectedClientsにRangeS-Sロックがあり、右側のノードをブロックします。これは、グラフXMLでは説明されていません。

パズルの最後のピースである、PK_ConnectedClientsにある原因不明のロック左ノードは、InsertOrUpdateのEF実装からのものであると想定します。おそらく最初にルックアップを実行し、ConnectedClientsとLogEntriesの間で宣言されたFK関係のために、PK_ConnectedClientsをシークし、シリアル化可能なロックを取得します。

ここでの主な原因は、トランザクション分離レベル(Serializable)とInsertOrUpdateでのEFの動作です。EFの動作についてアドバイスすることはできませんが、シリアル化可能なレベルは確かにやり過ぎです。これにより、読み取りコミットレベルで発生するエラーに戻ります。これは、残念ながら、コメントできないEFエラーです。

于 2010-03-21T01:15:14.310 に答える