誰かが次のトランザクションがデッドロックする理由を理解するのを助けてくれませんか?各テーブルに2つのトランザクションとインデックスを提供しています。
トランザクション1:
SELECT blockId, blockData, syncedToGeneration, itemId
FROM blocks
WHERE indexId=@indexId
and itemId IN (SELECT itemId
FROM KeywordReferences
WHERE indexId=@indexId
AND keywordRootId IN (360,4498,359,1229))
トランザクション2:
UPDATE blocks
SET blockData=@blockData,
syncedToGeneration=@syncedToGeneration
WHERE indexId=@indexId
AND blockId=@blockId
(最初のトランザクションの「IN」セクションがはるかに長く、約30の値が含まれていることに注意してください。読みやすくするために切り捨てています)
ブロックテーブルには次のインデックスがあります。
-indexId-> blockId(クラスター化)
-indexId-> itemId
indexId-> itemId
KeywordReferencesテーブルには、次のインデックスがあります。
-indexId_> keywordRootId(クラスター化)
-indexId-> keywordRootId-> score
-indexId-> itemId
-indexId-> blockId
以下はデッドロックグラフのxml出力です。
<deadlock-list>
<deadlock victim="process5a274c8">
<process-list>
<process id="process5a274c8" taskpriority="0" logused="0" waitresource="PAGE: 5:1:91671" waittime="2709" ownerId="122348" transactionname="SELECT" lasttranstarted="2012-04-19T21:23:26.680" XDES="0xf382aca0" lockMode="S" schedulerid="4" kpid="10992" status="suspended" spid="69" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2012-04-19T21:23:26.650" lastbatchcompleted="2012-04-19T21:23:26.647" clientapp=".Net SqlClient Data Provider" hostname="AMIT-PC" hostpid="6752" loginname="sa" isolationlevel="read committed (2)" xactid="122348" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="34" sqlhandle="0x020000002e9a3633b816ffc89dc234b4c0351887e4e1b2cf">
SELECT blockId, blockData, syncedToGeneration, itemId FROM blocks WHERE indexId=@indexId and itemId IN (SELECT itemId FROM KeywordReferences WHERE indexId=@indexId AND keywordRootId IN (360,4498,359,1229,2143,14330,7661,3755,1156,21490,5567,1933,429,28197,2,3165,524,3182,2655,27262,17407,2673,570,1478,3802,6838,19668,17,6586,2484,2794,1640,5171,2558,6592,5833,695,1199,2307,335,1351,6651,6899,3740,7048,22030,14356,597,3175,3965,3297,2711,14484,2761,2265,28,1647,3223,226,304,298,1157,197,2696,21172,19149,9,1159,135,1,3166,23325)) </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@indexId bigint)SELECT blockId, blockData, syncedToGeneration, itemId FROM blocks WHERE indexId=@indexId and itemId IN (SELECT itemId FROM KeywordReferences WHERE indexId=@indexId AND keywordRootId IN (360,4498,359,1229,2143,14330,7661,3755,1156,21490,5567,1933,429,28197,2,3165,524,3182,2655,27262,17407,2673,570,1478,3802,6838,19668,17,6586,2484,2794,1640,5171,2558,6592,5833,695,1199,2307,335,1351,6651,6899,3740,7048,22030,14356,597,3175,3965,3297,2711,14484,2761,2265,28,1647,3223,226,304,298,1157,197,2696,21172,19149,9,1159,135,1,3166,23325)) </inputbuf>
</process>
<process id="process5a13b88" taskpriority="0" logused="215304" waitresource="PAGE: 5:1:91669" waittime="2910" ownerId="128212" transactionname="user_transaction" lasttranstarted="2012-04-19T21:23:28.567" XDES="0xedcd9000" lockMode="IX" schedulerid="2" kpid="5500" status="suspended" spid="68" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2012-04-19T21:23:29.007" lastbatchcompleted="2012-04-19T21:23:29.007" clientapp=".Net SqlClient Data Provider" hostname="AMIT-PC" hostpid="6752" loginname="sa" isolationlevel="read committed (2)" xactid="128212" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="154" sqlhandle="0x02000000f4d83b0df2bfedc5a346288c21fa78e07eb152f6">
UPDATE blocks SET blockData=@blockData, syncedToGeneration=@syncedToGeneration WHERE indexId=@indexId AND blockId=@blockId </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@indexId bigint,@blockId int,@blockData ntext,@syncedToGeneration int)UPDATE blocks SET blockData=@blockData, syncedToGeneration=@syncedToGeneration WHERE indexId=@indexId AND blockId=@blockId </inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="91671" dbid="5" objectname="isqdb.dbo.blocks" id="lock5c54700" mode="IX" associatedObjectId="72057594043826176">
<owner-list>
<owner id="process5a13b88" mode="IX"/>
</owner-list>
<waiter-list>
<waiter id="process5a274c8" mode="S" requestType="wait"/>
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="91669" dbid="5" objectname="isqdb.dbo.blocks" id="lock5b84780" mode="S" associatedObjectId="72057594043826176">
<owner-list>
<owner id="process5a274c8" mode="S"/>
</owner-list>
<waiter-list>
<waiter id="process5a13b88" mode="IX" requestType="wait"/>
</waiter-list>
</pagelock>
</resource-list>
</deadlock>
</deadlock-list>
これは、テーブルスキャンの典型的なケースのように見えます(インデックスが欠落している可能性がありますが、それ以上になる可能性もあります)。 SELECTは、(blocks
テーブルでの)大規模なスキャンを示すページロックの粒度を選択しました。また、すべてのロックが同じリソース行セット(クラスター化インデックス)にどのように適用されているかにも注意してください。これは、SELECTが行を見つけるために選択的セカンダリインデックスを使用しないことを示しています。存在していると説明している(indexId, itemId)
の非クラスター化インデックスが選択され、クエリがデッドロックしないことを期待していたようです。現状では、選択性のために無視される可能性が最も高いようです( 転換点 にヒットします)。ワークロードとデータモデルを知らなくても、より良いインデックスをアドバイスすることは困難です。少なくとも、SELECTリストにblockData, syncedToGeneration
を投影する必要があるため、インデックスがカバーされなくなるため、最初に、インクルード列として追加することができます。しかし、これらの列のサイズ(blockData
は大きな列の名前のように見えます...)を知らない場合も、結果を予測することは困難です。
スキャンが原因である理由を例示するために、UPDATEステートメントによって更新された2つの行を検討してください。 UPDATEは(indexId, blockId)
の順序でそれらを更新しますが、SELECTスキャンは(indexId, itemId)
の順序でそれらを参照します。 2つのインデックスキーが同じ順序ですべての行を返すことを保証するitemId
とblockId
の間に機能的な依存関係がない限り、順序が逆になる行のペアが常に存在しますクラスタ化インデックスと非クラスタ化インデックスの間。その要点は、SELECTがスキャンを行うため、every行にアクセスすることが保証されているということです。したがって、UPDATEによって更新されたanyペアの行は、スキャンによってアクセスされます。 4つの可能性があります。
この状況は常に、スキャンがある場合に複数行の更新で発生します。通常、解決策は、スキャンをより選択的な操作に置き換えることです。これにより、パフォーマンスが向上するという追加の利点があります。 SELECTとUPDATEが本当に処理したい行のみを訪問する場合、同じ論理データで2つの操作が同時に発生する場合(つまり、同じ項目が同時に読み取られ更新される場合)にのみデッドロックが発生します。 OLTPシステムでは、これはビジネスワークフローによって防止されます。これは発生する可能性がありますが、頻度は大幅に低下します。補足として、分離した論理アイテムで操作している場合でも、 ハッシュの衝突による確率のゲーム 。
簡単な方法の1つは、実際に snapshot isolation をデプロイすることです。ただし、根本的な問題(スキャン)はマスクされるだけで、緩和されることはありません。スキャンから発生する他の問題(パフォーマンスの低下、待ち時間が長く、応答時間が遅い、バッファープールの汚染など)が発生することもあります。スキャンの問題を修正し、スナップショット分離をデプロイする必要があります。
追伸スキャンがPAGE細分度ロックを使用するという事実には関連性がないことに注意してください。スキャンがROWの粒度を使用している場合、デッドロックはページではなく行で発生します。しかし、ページの細分性が選択されているという事実は、スキャンの証拠として重要です。
これは考えられる説明のようです:
Selectは、itemIdの非クラスター化インデックスの共有ロックを保持し、クラスター化インデックスの別の共有ロックの取得を待機して、より多くの列を取得できるようにします。
更新により、クラスター化インデックスのページが変更され、もちろん排他ロックが保持されます。非クラスター化インデックスの変更も待機します。
関連するテーブルとインデックスのDDLを投稿できますか?
デッドロックの原因は、エンジンがそれらのitemid値のそれぞれに対して一度に1つずつ行ロックを取得することにあると思います。各クエリは、itemid値のソートされていないリストで作業しており、それらのロックを異なる順序で取得し、それらが絡み合うことを可能にします。最終的に、各クエリは、他のクエリがすでに持っているロックで終了し、他のクエリが必要とするロックを持ちます。もう1つは、SQLがロックをテーブルロックに昇格しようとしている可能性があることです。 SELECTSが役立つ接続でSET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDを実行するか、droppingページまたはテーブルのロックの細分性によってデッドロックを解消することができます。