4


3

これら2つのSQLステートメントがデッドロックするのはなぜですか? (デッドロックグラフ+詳細を含む)

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

メインデッドロックグラフ

alt text http://img140.imageshack.us/img140/6193/deadlock1.png http://img140.imageshack.us/img140/ 6193 / deadlock1.png [画像を拡大するにはここをクリックしてください。]

左側、詳細

alt text http://img715.imageshack.us/img715/3999/deadlock2.png http://img715.imageshack.us/img715/ 3999 / deadlock2.png [画像を拡大するにはここをクリックしてください。]

右側、詳細

alt text http://img686.imageshack.us/img686/5097/deadlock3.png http://img686.imageshack.us/img686/ 5097 / deadlock3.png [画像を拡大するにはここをクリックしてください。]

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

http://www.2shared.com/file/12235457/d3637111/deadlock.html [ここをクリックしてxmlファイルをダウンロード]。

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

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

多数のファイルを読み込んでいます(例: この例では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`インスタンスがあります(つまり、独自のデータベース接続、トランザクション、リポジトリコンテキストがあります)。 だから、これはdbへの3つの異なる接続がすべて同時に起こっていることを意味すると仮定しています。

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

プロファイリングツールを使用すると、「Isolation Level」は「Serializable」になります。 「ReadCommited」と「ReadUncommited」も試しましたが、どちらもエラーです:-

  • ReadCommited:上記と同じ。 デッドロック

  • ReadUncommited:別のエラー。 予想されるEF例外 いくつかの結果が返されましたが、何も得られませんでした。 これは、予想されるが、ダーティリードのために取得されない LogEntryId Identity(` scope_identity`)値だと推測しています。

助けてください!

PS. それはSql Server 2008です。

'' '' '

更新#2

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

EF図

今、Remusは提案します(そして、彼はEFに不慣れだと言っています)。

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

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

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);
    }
}

Hmm. 単純な AddObject(別名。 Insert)または Attach(別名。 更新)。 参照なし また、SQLコードは、ルックアップ項目のヒントもありません。

OK 私には他に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投稿の最初までの私の最初の論理的な方法で、そのメソッド呼び出しをコメントアウトすると…​ 何が起こるのですか?

できます。 WOWZ。

また、「Serializable」または「Read Commited」として機能します。「Delete」メソッドを呼び出さない場合は両方とも機能します。

*では、なぜその削除メソッドはロックを取得しているのでしょうか?

「コミット済みの読み取り」では、同時に3つの削除呼び出しが発生する可能性があります。

  • 最初は、データのインスタンスを取得します。

  • 2番目(および3番目)は、同じデータの別のインスタンスを取得します。

  • さて、最初の削除。 大丈夫です。

  • 2番目の削除.. しかし、行がなくなった.. だから私はその奇妙なを取得します についてのエラーは、予期しない行数(0)に影響しました。⇐=ゼロのアイテムが削除されました。

可能? もしそうなら.. あー… どうすればこれを修正できますか? これは競合状態の典型的なケースですか? これがどういうわけか起こらないようにすることは可能ですか?

'' '' '

更新情報

  • 画像へのリンクを修正しました。

  • 生のXMLデッドロックファイルへのリンク。 Here http://www.2shared.com/file/12235457/d3637111/deadlock.html [同じリンク]。

  • データベーステーブルスキーマを追加しました。

  • 両方のテーブルの詳細を追加しました。

1 Answer


4


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

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

他の人が言ったように、アクセスパス(使用されるインデックス)は重要なので、デッドロック評価でデータベーススキーマをポストする必要があります。 デッドロックにおけるインデックスの意味についての詳細な議論については、私の記事http://rusanu.com/2009/05/16/readwrite-deadlock/[Read-Write deadlock]をご覧ください。

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

また、デッドロックグラフの写真を投稿しないでください。 画像は、1000の単語がここでは正しくないことを示しています。元のデッドロックXMLを投稿してください。きれいな画像には見えない多くの情報があります。

更新

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

デッドロックの完全な説明は次のとおりです。

  • 右側のノードは_connectedClientRepository.Insertにあり、範囲が必要です PK_ConnectedClientsにロックを挿入します。 以前に実行した_logEntryRepository.InsertOrUpdateからのフルテキストインデックスi1にRangeS-Uロックがあります。

  • 左側のノードは、_logEntryRepository.InsertOrUpdate、INSERT バッチ内のステートメント、およびフルテキストインデックスi1のRangeS-Uロックが必要です。 PK_ConnectedClientsに正しいノードをブロックするRangeS-Sロックがありますが、これはグラフXMLで説明されていません。

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

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