web-dev-qa-db-ja.com

SQLServerでのデッドロックのトラブルシューティング

編集:必要に応じて、JDBCを介してJava webappでデータベースに接続します。これらすべての操作にautocommit=offを使用します。デッドロックは、ジョブスレッドがコミットされていないことを意味します。


SQLServerの断続的なデッドロックの問題を解決しようとしています。デッドロックレポートがあり、2つのクエリがどのように競合しているかを理解できません。

関係するスレッドは2つあります。1つはXという名前のテーブルでentitiesロックを取得してから、Uという名前のテーブルでrevisionsロックを取得しようとします。 _。その間、別のスレッドがXrevisionsロックし、Uentitiesロックしようとしています。この質問では、entitiesテーブルに焦点を当てます。

ここで私を混乱させているのは、Xentitiesロックを作成したクエリを見つけたと思いますが、このロックがUリクエストとどのように競合するかわかりません。

以下は、Xロッククエリであると私が推測しているクエリの例です。

INSERT INTO entities(
  uuid,
  entity_type,
  internal_id,
  date_modified) VALUES (
  @P0,
  'USER',
  null,
  getdate()
)

このテーブルには2つのインデックスがあります。主キー(uuid)と一意のインデックス(entity_type、internal_id)です。

デッドロックレポートは、関連するインデックスが(entity_type、internal_id)をカバーする一意のインデックスであると主張しています。テストにより、上記のステートメントが実際にクラスター化インデックス(uuid)を使用して行のXロックを作成することを確認しましたが、一意のインデックスのXロックも作成します。トランザクションを使用してSSMSでこれをテストし、sys.dm_tran_locksテーブルにクエリを実行して、作成されたロックを確認しました。

ただし、entitiesテーブルにUロックを作成しているクエリは、主キーにのみロックを作成するため、そのインデックスをロックしているようには見えません。これがクエリです:

UPDATE entities SET
  head_rev_id = @P0,
  date_modified = getdate()
WHERE uuid = @P1

両方のクエリにある実際のパラメーターを確認できないことに注意してください。ただし、これらのクエリが最終的にどのように実行されるかというコンテキストに基づいて、それらが同じである可能性がある論理的な理由はわかりません。 (サイドの質問:クエリに渡されるパラメーターを確認する方法はありますか?TRACE 1222を有効にしましたが、拡張イベントにあったデッドロックレポート以外の詳細を取得していません。)試しました2番目のトランザクションで同じエンティティを更新すると、sys.dm_tran_locksビューは、クラスター化インデックスのみを使用して、待機状態のXロックを持っていることを示します。

これがデッドロックレポートのxmlです。多分私はこれを間違って読んでいますが、他に何を探すことができますか?

<deadlock>
 <victim-list>
  <victimProcess id="process476f3dc38" />
 </victim-list>
 <process-list>
  <process id="process476f3dc38" taskpriority="0" logused="1768" waitresource="KEY: 66:72057594135838720 (c3a138dec5f7)" waittime="4831" ownerId="7413985" transactionname="implicit_transaction" lasttranstarted="2020-01-06T17:17:18.320" XDES="0x46f5363a8" lockMode="U" schedulerid="4" kpid="3064" status="suspended" spid="108" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2020-01-06T17:17:18.440" lastbatchcompleted="2020-01-06T17:17:18.437" lastattention="1900-01-01T00:00:00.437" clientapp="Microsoft JDBC Driver for SQL Server" hostname="xxx" hostpid="0" loginname="xxx" isolationlevel="read committed (2)" xactid="7413985" currentdb="66" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
   <executionStack>
    <frame procname="adhoc" line="1" stmtstart="78" sqlhandle="0x0200000045fed007bf27432ade4d4e079e599714526613ff0000000000000000000000000000000000000000">
UPDATE revisions SET state = @P0 WHERE uuid = @P1    </frame>
    <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
   </executionStack>
   <inputbuf>
(@P0 nvarchar(4000),@P1 nvarchar(4000))UPDATE revisions SET state = @P0 WHERE uuid = @P1                   </inputbuf>
  </process>
  <process id="process476f37c38" taskpriority="0" logused="10568" waitresource="KEY: 66:72057594133544960 (4c37a96d5030)" waittime="4831" ownerId="7413919" transactionname="implicit_transaction" lasttranstarted="2020-01-06T17:17:18.290" XDES="0x4744323a8" lockMode="U" schedulerid="7" kpid="1920" status="suspended" spid="85" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2020-01-06T17:17:18.440" lastbatchcompleted="2020-01-06T17:17:18.440" lastattention="1900-01-01T00:00:00.440" clientapp="Microsoft JDBC Driver for SQL Server" hostname="xxx" hostpid="0" loginname="xxx" isolationlevel="read committed (2)" xactid="7413919" currentdb="66" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
   <executionStack>
    <frame procname="adhoc" line="1" stmtstart="78" sqlhandle="0x02000000ab882b34a7f1c9e08c7ff4a42d357133d4972a960000000000000000000000000000000000000000">
UPDATE entities SET
  head_rev_id = @P0,
  date_modified = getdate()
WHERE uuid = @P1    </frame>
    <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
   </executionStack>
   <inputbuf>
(@P0 nvarchar(4000),@P1 nvarchar(4000))UPDATE entities SET
  head_rev_id = @P0,
  date_modified = getdate()
WHERE uuid = @P1                   </inputbuf>
  </process>
 </process-list>
 <resource-list>
  <keylock hobtid="72057594135838720" dbid="66" objectname="revisions" indexname="revisions_parent_idx" id="lock2cfaf0e00" mode="X" associatedObjectId="72057594135838720">
   <owner-list>
    <owner id="process476f37c38" mode="X" />
   </owner-list>
   <waiter-list>
    <waiter id="process476f3dc38" mode="U" requestType="wait" />
   </waiter-list>
  </keylock>
  <keylock hobtid="72057594133544960" dbid="66" objectname="entities" indexname="unique_entities" id="lock2e75ddc80" mode="X" associatedObjectId="72057594133544960">
   <owner-list>
    <owner id="process476f3dc38" mode="X" />
   </owner-list>
   <waiter-list>
    <waiter id="process476f37c38" mode="U" requestType="wait" />
   </waiter-list>
  </keylock>
 </resource-list>
</deadlock>

問題の2つのテーブルのスキーマは次のとおりです。

CREATE TABLE [entities]
(
    [uuid]          [varchar](100) NOT NULL,
    [entity_type]   [varchar](100) NOT NULL,
    [internal_id]   [varchar](255) NULL,
    [date_modified] [datetime2](6) NOT NULL,
    [head_rev_id]   [varchar](100) NULL,
    [test]          [varchar](100) NULL,
    CONSTRAINT [entities_pk] PRIMARY KEY CLUSTERED
        (
         [uuid] ASC
            ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON),
    CONSTRAINT [unique_entities] UNIQUE NONCLUSTERED
        (
         [entity_type] ASC,
         [internal_id] ASC
            ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

ALTER TABLE [entities]
    WITH CHECK ADD CONSTRAINT [entities_revision_fk] FOREIGN KEY ([head_rev_id])
        REFERENCES [revisions] ([uuid])

ALTER TABLE [entities]
    WITH CHECK ADD CONSTRAINT [entity_types_entities_fk] FOREIGN KEY ([entity_type])
        REFERENCES [entity_types] ([entity_type])
        ON DELETE CASCADE

CREATE TABLE [revisions]
(
    [entity_id]   [varchar](100)   NOT NULL,
    [uuid]        [varchar](100)   NOT NULL,
    [parent_uuid] [varchar](100)   NULL,
    [priority]    [numeric](20, 0) NOT NULL,
    [state]       [varchar](50)    NOT NULL,
    CONSTRAINT [revisions_pk] PRIMARY KEY CLUSTERED
        (
         [uuid] ASC
            ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

ALTER TABLE [revisions]
    WITH CHECK ADD CONSTRAINT [rev_entity] FOREIGN KEY ([entity_id])
        REFERENCES [entities] ([uuid])
        ON DELETE CASCADE

ALTER TABLE [revisions]
    WITH CHECK ADD CONSTRAINT [rev_rev_state] FOREIGN KEY ([state])
        REFERENCES [revision_states] ([state])

CREATE NONCLUSTERED INDEX [revisions_entity_idx] ON [revisions]
    (
     [entity_id] ASC
        ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [revisions_parent_idx] ON [revisions]
    (
     [parent_uuid] ASC
        ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

CREATE UNIQUE NONCLUSTERED INDEX [u_duplicate_unassigned] ON [revisions]
    (
     [entity_id] ASC,
     [state] ASC
        )
    WHERE ([state] = 'UNASSIGNED')
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
2
Mirrana

以前にこのタイプのデッドロックに遭遇したことがあります。問題は、entitiesテーブルに対するrevisionsテーブルへの外部キー検証が、revisionsテーブルへの更新によってブロックされていることです。これは、プロセスの1つを効率化するためのキーがrevisionsにないこと(おそらくuuidフィールドのキー)が原因で発生し、参照の検索中にPKをスキャンします。これは、インデックスのクロスリクエストページのクエリをブロックします(注:これは私の理論-確認していませんが、あたかも本当であるかのように操作すると、これらの問題が以前に修正されました) 。私はお勧め:

CREATE INDEX Idx_Revisions_uuid ON revisions (uuid)

おそらく、他のINCLUDEデータ、またはインデックスフィールドを使用するのが最善です。これが機能するためには、uuidフィールドがインデックスの最初のフィールドでなければならないことを覚えておいてください。

1
Laughing Vergil