互いにデッドロックしている 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 ファイル
テーブル スキーマ
代替テキスト 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 Level
、Serializable
. 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 デッドロック ファイルへのリンク。ここに同じリンクがあります。
- データベース テーブル スキーマが追加されました。
- 両方の表の詳細を追加しました。