web-dev-qa-db-ja.com

挿入と削除によるデッドロック

デッドロックグラフ:

_<deadlock>
  <victim-list>
    <victimProcess id="process21881f1bc28" />
  </victim-list>
  <process-list>
    <process id="process21881f1bc28" taskpriority="0" logused="0" waitresource="KEY: 7:72057594049003520 (44e4a8141ab4)" waittime="375" ownerId="313642186" transactionname="user_transaction" lasttranstarted="2018-12-09T10:00:39.007" XDES="0x217706d6408" lockMode="RangeS-U" schedulerid="2" kpid="6776" status="suspended" spid="59" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-12-09T10:00:39.013" lastbatchcompleted="2018-12-09T10:00:39.007" lastattention="1900-01-01T00:00:00.007" clientapp=".Net SqlClient Data Provider" hostname="x" hostpid="4724" loginname="x" isolationlevel="read uncommitted (1)" xactid="313642186" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="adhoc" line="1" stmtstart="44308" stmtend="72024" sqlhandle="0x02000000fb261007e17c6dabc0917779a6823e14abd04fef0000000000000000000000000000000000000000">
unknown    </frame>
        <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
      </executionStack>
      <inputbuf>
(@param0 int,@param1 int,@param2 int,@param3 real,@param4 int,@param5 int,@param6 int,@param7 nvarchar(21),@param8 int,@param9 nvarchar(7),@param10 datetime,@param11 datetime,@param12 nvarchar(14),@param13 int,@param14 int,@param15 int,@param16 real,@param17 int,@param18 int,@param19 int,@param20 nvarchar(21),@param21 int,@param22 nvarchar(7),@param23 datetime,@param24 datetime,@param25 nvarchar(14),@param26 int,@param27 int,@param28 int,@param29 real,@param30 int,@param31 int,@param32 int,@param33 nvarchar(21),@param34 int,@param35 nvarchar(12),@param36 datetime,@param37 datetime,@param38 nvarchar(14),@param39 int,@param40 int,@param41 int,@param42 real,@param43 int,@param44 int,@param45 int,@param46 nvarchar(21),@param47 int,@param48 nvarchar(12),@param49 datetime,@param50 datetime,@param51 nvarchar(14),@param52 int,@param53 int,@param54 int,@param55 real,@param56 int,@param57 int,@param58 int,@param59 nvarchar(21),@param60 int,@param61 nvarchar(7),@param62 datetime,@param63 datetime,@param64 nvarchar(14),  /*INSERT INTO TradeBuffer_US_PC .... VALUES ...*/
 </inputbuf>
    </process>
    <process id="process2187b184108" taskpriority="0" logused="4655716" waitresource="KEY: 7:72057594049003520 (233cbd3694c1)" waittime="82" ownerId="313639357" transactionname="DELETE" lasttranstarted="2018-12-09T10:00:27.050" XDES="0x2188843d8a8" lockMode="X" schedulerid="1" kpid="6340" status="suspended" spid="61" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-12-09T10:00:00.250" lastbatchcompleted="2018-12-09T10:00:00.250" lastattention="1900-01-01T00:00:00.250" clientapp="SQLAgent - TSQL JobStep (Job 0x19A4A8C8E6580E42BAB84B11DDD66C97 : Step 1)" hostname="HWC-HWP-1098250" hostpid="4044" loginname="NT SERVICE\SQLSERVERAGENT" isolationlevel="read committed (2)" xactid="313639357" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="adhoc" line="27" stmtstart="7180" stmtend="7278" sqlhandle="0x0200000013d8441152a66453df9df52295e0815f8e2c4a0b0000000000000000000000000000000000000000">
unknown    </frame>
      </executionStack>
      <inputbuf>
SET QUOTED_IDENTIFIER ON
DECLARE @LastID INT
SELECT TOP 1 @LastID = ID FROM [TradeBuffer_US_PC] ORDER BY ID DESC

IF @LastID IS NOT NULL
BEGIN
    BEGIN TRY
        ...
        DELETE FROM TradeBuffer_US_PC WHERE ID < @LastID
      </inputbuf>
    </process>
  </process-list>
  <resource-list>
    <keylock hobtid="72057594049003520" dbid="7" objectname="TradeBuffer_US_PC" indexname="UX_TradeBuffer_US_PC_PlayerID_ItemID_Amount_TotalPrice_ExpireTime_GuildName" id="lock217fdb50980" mode="X" associatedObjectId="72057594049003520">
      <owner-list>
        <owner id="process2187b184108" mode="X" />
      </owner-list>
      <waiter-list>
        <waiter id="process21881f1bc28" mode="RangeS-U" requestType="wait" />
      </waiter-list>
    </keylock>
    <keylock hobtid="72057594049003520" dbid="7" objectname="TradeBuffer_US_PC" indexname="UX_TradeBuffer_US_PC_PlayerID_ItemID_Amount_TotalPrice_ExpireTime_GuildName" id="lock2178726f880" mode="RangeS-U" associatedObjectId="72057594049003520">
      <owner-list>
        <owner id="process21881f1bc28" mode="RangeS-U" />
      </owner-list>
      <waiter-list>
        <waiter id="process2187b184108" mode="X" requestType="wait" />
      </waiter-list>
    </keylock>
  </resource-list>
</deadlock>
_

IDはプライマリIDキーです。

最初のプロセスはマルチ挿入(INSERT INTO ... VALUES(...), (...), (...))を実行しており、2番目のプロセスは同じテーブルで削除を実行しています。

2番目のプロセスは、バッファテーブルを実際のテーブルにマージし、バッファテーブルをパージすることです。

バッファテーブルには、_IGNORE_DUP_KEY_をオンにして定義された一意のキーがあります。したがって、マルチ挿入には、すでにバッファテーブルにある重複行が含まれている可能性があります。

グラフから、削除プロセスは排他ロックの所有者であり、挿入プロセスは更新ロックを取得して挿入するのを待っているようです。

しかし、2番目のキーロックブロックは、挿入プロセスが更新ロック(新しい行の1つが挿入されたと想定)を所有し、削除プロセスが排他ロックを取得しようとしていることを示しています。

削除プロセスがすでに排他ロックを所有しているので、これは私には奇妙に見えます。なぜそれを再度要求して失敗するのですか?

削除プロセスはSQLエージェントジョブ内で実行されており、_begin trans_ブロックでラップされていません

この場合、デッドロックが発生するのはなぜですか?それを回避するために私ができることはありますか?

3
Steve

挿入プロセス(process21881f1bc28)は、テーブルTradeBuffer_US_PCの非クラスター化インデックスのキーに対して部分的な範囲ロックを持っていますが、範囲内の残りのキーを待機しています。

削除プロセス(process2187b184108)は、同じ範囲のキーの1つに排他ロックをかけ、その範囲の他のキーの1つを削除するロックを待機しています。

これに関連して、次の非常に詳細なブログ投稿を確認してください。

Microsoft SQL Serverでのロック(パート20)– IGNORE_DUP_KEYインデックスオプションによる範囲ロック(RangeS-U)デッドロック

著者は、IGNORE_DUP_KEY非クラスター化インデックスを含むテーブルに挿入すると、非クラスター化インデックスキーでSERIALIZABLEロック動作が発生する可能性があることを示しています。これは、SQL Serverが提供する最も制限的なロック動作であり、同時実行性を最も低下させます。

残念ながら、これは、このデッドロックの状況を緩和するためにあなたの手にいくつかの作業があることを意味します。以下は、コードを確認していなくても役立つかもしれない一般的なアイデアです。

  • 最も手間のかかるオプションは、現在よりも小さいバッチで行を削除することです。これは、遭遇するデッドロックの数を少なくともlimitにします。

  • おそらく大きな変更は、deleteプロセスでSERIALIZBLE分離レベルを明示的に使用し、範囲全体に対してSELECTを実行することです。最初に削除すると予想されるキーの数。これにより、削除する必要があるロックの範囲が取得されます。

  • 別のオプションは、注文を利用することです。挿入プロセスと削除プロセスの両方を変更して、同じ順序(たとえば、キーの昇順)でデータを操作するようにします。これにより、ロックが同じ順序で取得されることが期待され、デッドロックが発生する可能性が低くなります(代わりに、互換性のないロックが解放されるのを待つだけです)。

PS:挿入プロセスのプロセスノードでこれに気づきました:

isolationlevel="read uncommitted (1)"

このデータ変更クエリにREAD UNCOMMITTEDを使用している理由はわかりませんが、期待どおりに機能していない可能性があります。

5
Josh Darnell