web-dev-qa-db-ja.com

SQLで1つの読み取りスレッドと1つの書き込みスレッドでデッドロックが発生するのはなぜですか

誰かが次のトランザクションがデッドロックする理由を理解するのを助けてくれませんか?各テーブルに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>
4
Amit Ben Shahar

これは、テーブルスキャンの典型的なケースのように見えます(インデックスが欠落している可能性がありますが、それ以上になる可能性もあります)。 SELECTは、(blocksテーブルでの)大規模なスキャンを示すページロックの粒度を選択しました。また、すべてのロックが同じリソース行セット(クラスター化インデックス)にどのように適用されているかにも注意してください。これは、SELECTが行を見つけるために選択的セカンダリインデックスを使用しないことを示しています。存在していると説明している(indexId, itemId)の非クラスター化インデックスが選択され、クエリがデッドロックしないことを期待していたようです。現状では、選択性のために無視される可能性が最も高いようです( 転換点 にヒットします)。ワークロードとデータモデルを知らなくても、より良いインデックスをアドバイスすることは困難です。少なくとも、SELECTリストにblockData, syncedToGenerationを投影する必要があるため、インデックスがカバーされなくなるため、最初に、インクルード列として追加することができます。しかし、これらの列のサイズ(blockDataは大きな列の名前のように見えます...)を知らない場合も、結果を予測することは困難です。

スキャンが原因である理由を例示するために、UPDATEステートメントによって更新された2つの行を検討してください。 UPDATEは(indexId, blockId)の順序でそれらを更新しますが、SELECTスキャンは(indexId, itemId)の順序でそれらを参照します。 2つのインデックスキーが同じ順序ですべての行を返すことを保証するitemIdblockIdの間に機能的な依存関係がない限り、順序が逆になる行のペアが常に存在しますクラスタ化インデックスと非クラスタ化インデックスの間。その要点は、SELECTがスキャンを行うため、every行にアクセスすることが保証されているということです。したがって、UPDATEによって更新されたanyペアの行は、スキャンによってアクセスされます。 4つの可能性があります。

  • 更新された両方の行が、スキャンの現在の位置(現在スキャンされている行)の後ろに(クラスター化インデックスキー順で)遅れています。これは安全です。UPDATEはブロックし、スキャンを待機します。
  • 更新された両方の行がスキャンの位置より前にあります。また、スキャンはブロックされ、更新を待機します。
  • uPDATEdの最初の行はスキャンの現在の位置より後ろですが、2番目の行は前です。安全、更新はブロックします。
  • uPDATEdの最初の行はスキャンの現在の位置より前ですが、2番目の行は後ろです。これは保証付きデッドロックです。さらに離れた行がクラスター化インデックスの順序にある​​ほど、1つが前方にあり、もう1つが後方にある可能性が高いことに注意してください任意のスキャン位置。

この状況は常に、スキャンがある場合に複数行の更新で発生します。通常、解決策は、スキャンをより選択的な操作に置き換えることです。これにより、パフォーマンスが向上するという追加の利点があります。 SELECTとUPDATEが本当に処理したい行のみを訪問する場合、同じ論理データで2つの操作が同時に発生する場合(つまり、同じ項目が同時に読み取られ更新される場合)にのみデッドロックが発生します。 OLTPシステムでは、これはビジネスワークフローによって防止されます。これは発生する可能性がありますが、頻度は大幅に低下します。補足として、分離した論理アイテムで操作している場合でも、 ハッシュの衝突による確率のゲーム

簡単な方法の1つは、実際に snapshot isolation をデプロイすることです。ただし、根本的な問題(スキャン)はマスクされるだけで、緩和されることはありません。スキャンから発生する他の問題(パフォーマンスの低下、待ち時間が長く、応答時間が遅い、バッファープールの汚染など)が発生することもあります。スキャンの問題を修正しスナップショット分離をデプロイする必要があります。

追伸スキャンがPAGE細分度ロックを使用するという事実には関連性がないことに注意してください。スキャンがROWの粒度を使用している場合、デッドロックはページではなく行で発生します。しかし、ページの細分性が選択されているという事実は、スキャンの証拠として重要です。

8
Remus Rusanu

これは考えられる説明のようです:

  1. Selectは、itemIdの非クラスター化インデックスの共有ロックを保持し、クラスター化インデックスの別の共有ロックの取得を待機して、より多くの列を取得できるようにします。

  2. 更新により、クラスター化インデックスのページが変更され、もちろん排他ロックが保持されます。非クラスター化インデックスの変更も待機します。

関連するテーブルとインデックスのDDLを投稿できますか?

3
A-K

デッドロックの原因は、エンジンがそれらのitemid値のそれぞれに対して一度に1つずつ行ロックを取得することにあると思います。各クエリは、itemid値のソートされていないリストで作業しており、それらのロックを異なる順序で取得し、それらが絡み合うことを可能にします。最終的に、各クエリは、他のクエリがすでに持っているロックで終了し、他のクエリが必要とするロックを持ちます。もう1つは、SQLがロックをテーブルロックに昇格しようとしている可能性があることです。 SELECTSが役立つ接続でSET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDを実行するか、droppingページまたはテーブルのロックの細分性によってデッドロックを解消することができます。

2
darin strait