SQL Server Deadlock от IX блокировка страницы - PullRequest
0 голосов
/ 30 августа 2018

У меня тупик, и я не вижу, как это можно решить. Я уже исправил некоторые другие запросы, поступающие со стороны EF, которые вызывали такую ​​же тупиковую ситуацию (в той же строке в sp), но этот нельзя изменить, это очень простой запрос, и я думаю, что вместо этого должен быть более простой способ переписать SP или изменение индексов, чтобы избежать блокировок страниц.

Три таблицы:

  • Рабочие элементы (Id, DMC, ...)
  • Сборы (Id, ...)
  • ChargeItems (Id, Charge_Id, Workitem_Id, ...)

Два процесса:

  • EF-запрос, который вызывается много раз. (~ 5-10 / мин)
  • Хранимая процедура, которая архивирует данные путем перемещения архивов в другую базу данных и удаления их из источника.

тупик:

Он генерируется на самом последнем шаге, когда SP пытается удалить пустые Заряды, где у ChargeItems нет записей. К этому моменту он уже удалил все ChargeItems, и должны быть удалены только пустые расходы и рабочие элементы.

Deadlock graph

Запрос, выполняемый EF, ищет Workitem по DMC, в то время как SP пытается удалить Charges.

SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[DMC] AS [DMC], 
    [Limit1].[FirstSeen] AS [FirstSeen], 
    [Limit1].[DrawingNo] AS [DrawingNo], 
    [Limit1].[MachineId] AS [MachineId], 
    [Limit1].[WorkItemState_Id] AS [WorkItemState_Id], 
    [Limit1].[ItemType_Id] AS [ItemType_Id], 
    [Limit1].[Repaired] AS [Repaired], 
    [Limit1].[MachineCycle] AS [MachineCycle], 
    [Limit1].[FirstSeenCheck] AS [FirstSeenCheck], 
    [Limit1].[LastSeen] AS [LastSeen], 
    [Limit1].[Archive] AS [Archive], 
    [Limit1].[CastingDateString] AS [CastingDateString], 
    [Limit1].[Deleted] AS [Deleted], 
    [Limit1].[DMC2] AS [DMC2], 
    [Limit1].[Id1] AS [Id1], 
    [Limit1].[WorkPlace_Id] AS [WorkPlace_Id], 
    [Limit1].[CastingFormIdent_Id] AS [CastingFormIdent_Id], 
    [Limit1].[FormIdentItemType_Id] AS [FormIdentItemType_Id]
    FROM ( SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[DMC] AS [DMC], 
        [Extent1].[FirstSeen] AS [FirstSeen], 
        [Extent1].[DrawingNo] AS [DrawingNo], 
        [Extent1].[MachineId] AS [MachineId], 
        [Extent1].[WorkItemState_Id] AS [WorkItemState_Id], 
        [Extent1].[ItemType_Id] AS [ItemType_Id], 
        [Extent1].[Repaired] AS [Repaired], 
        [Extent1].[MachineCycle] AS [MachineCycle], 
        [Extent1].[FirstSeenCheck] AS [FirstSeenCheck], 
        [Extent1].[LastSeen] AS [LastSeen], 
        [Extent1].[Archive] AS [Archive], 
        [Extent1].[CastingDateString] AS [CastingDateString], 
        [Extent1].[Deleted] AS [Deleted], 
        [Extent1].[DMC2] AS [DMC2], 
        [Extent1].[WorkPlace_Id] AS [WorkPlace_Id], 
        [Extent1].[CastingFormIdent_Id] AS [CastingFormIdent_Id], 
        [Extent1].[FormIdentItemType_Id] AS [FormIdentItemType_Id], 
        [Extent2].[Id] AS [Id1]
        FROM WorkItems AS [Extent1]
        LEFT OUTER JOIN [dbo].[ChargeItems] AS [Extent2] ON [Extent1].[Id] = [Extent2].[WorkItem_Id]
        WHERE ([Extent1].[DMC] = '') OR (([Extent1].[DMC] IS NULL))
    )  AS [Limit1]

План выполнения выбора:

Execution plan of the select statement from a different run. (Not caused deadlock.)

Часть СП:

;with chargesToDelete(id, ciid) as (
                    select c.id, ci.Id from @chargeids c
                    left join dbo.chargeitems ci on ci.Charge_Id = c.id
                    where ci.id is null
                )

                delete from dbo.charges
                    where Id in (select id from chargesToDelete)

График тупиковой ситуации xml:

    <deadlock>
 <victim-list>
  <victimProcess id="process6472ad498" />
 </victim-list>
 <process-list>
  <process id="process6472ad498" taskpriority="5" logused="152924" waitresource="PAGE: 5:1:531207 " waittime="794" ownerId="10001638" transactionname="DELETE" lasttranstarted="2018-08-29T11:50:14.510" XDES="0x6ff07f078" lockMode="IX" schedulerid="7" kpid="7620" status="suspended" spid="89" sbid="0" ecid="0" priority="-5" trancount="2" lastbatchstarted="2018-08-29T11:22:53.457" lastbatchcompleted="2018-08-29T11:22:53.457" lastattention="1900-01-01T00:00:00.457" clientapp="Microsoft SQL Server Management Studio - Query" hostname="PCSERVER151" hostpid="6480" loginname="PRC\administrator" isolationlevel="read uncommitted (1)" xactid="10001638" currentdb="5" lockTimeout="4294967295" clientoption1="673187936" clientoption2="390200">
   <executionStack>
    <frame procname="LP_R.dbo.Archive_Finish" line="190" stmtstart="12876" stmtend="13044" sqlhandle="0x03000500063ecd76e962c80014a9000001000000000000000000000000000000000000000000000000000000">
delete from LP_R.dbo.workitems where id in (select id from @workitemIds);    </frame>
    <frame procname="LP_R.dbo.Archive" line="64" stmtstart="5142" stmtend="5244" sqlhandle="0x030005007886b57874f2b30014a9000001000000000000000000000000000000000000000000000000000000">
exec Archive_Finish @Day, @Dryrun, @MaxWorkitems;    </frame>
    <frame procname="adhoc" line="4" stmtstart="62" stmtend="200" sqlhandle="0x0100050010f3f82c309a63770600000000000000000000000000000000000000000000000000000000000000">
EXEC    @return_value = [dbo].[Archive]
        @Day = 450,
        @Dryrun = 0    </frame>
   </executionStack>
   <inputbuf>

DECLARE @return_value int

EXEC    @return_value = [dbo].[Archive]
        @Day = 450,
        @Dryrun = 0

SELECT  'Return Value' = @return_value

   </inputbuf>
  </process>
  <process id="process66f184558" taskpriority="0" logused="0" waitresource="PAGE: 5:1:114492 " waittime="913" ownerId="10002051" transactionname="SELECT" lasttranstarted="2018-08-29T11:50:15.210" XDES="0x6b379ad00" lockMode="S" schedulerid="5" kpid="3860" status="suspended" spid="67" sbid="2" ecid="0" priority="0" trancount="0" lastbatchstarted="2018-08-29T11:50:15.210" lastbatchcompleted="2018-08-29T11:50:15.210" lastattention="1900-01-01T00:00:00.210" clientapp="EntityFramework" hostname="PCSERVER151" hostpid="3520" loginname="sa" isolationlevel="read committed (2)" xactid="10002051" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
   <executionStack>
    <frame procname="adhoc" line="1" stmtstart="56" sqlhandle="0x02000000412fd7099fe0d3410b538a2193192ac8c5143cf20000000000000000000000000000000000000000">
SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[DMC] AS [DMC], 
    [Limit1].[FirstSeen] AS [FirstSeen], 
    [Limit1].[DrawingNo] AS [DrawingNo], 
    [Limit1].[MachineId] AS [MachineId], 
    [Limit1].[WorkItemState_Id] AS [WorkItemState_Id], 
    [Limit1].[ItemType_Id] AS [ItemType_Id], 
    [Limit1].[Repaired] AS [Repaired], 
    [Limit1].[MachineCycle] AS [MachineCycle], 
    [Limit1].[FirstSeenCheck] AS [FirstSeenCheck], 
    [Limit1].[LastSeen] AS [LastSeen], 
    [Limit1].[Archive] AS [Archive], 
    [Limit1].[CastingDateString] AS [CastingDateString], 
    [Limit1].[Deleted] AS [Deleted], 
    [Limit1].[DMC2] AS [DMC2], 
    [Limit1].[Id1] AS [Id1], 
    [Limit1].[WorkPlace_Id] AS [WorkPlace_Id], 
    [Limit1].[CastingFormIdent_Id] AS [CastingFormIdent_Id], 
    [Limit1].[FormIdentItemType_Id] AS [FormIdentItemType_Id]
    FROM ( SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[DMC] AS [DMC], 
        [Extent1].[FirstSeen] AS [FirstSeen], 
        [Extent1    </frame>
    <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
   </executionStack>
   <inputbuf>
(@p__linq__0 nvarchar(4000))SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[DMC] AS [DMC], 
    [Limit1].[FirstSeen] AS [FirstSeen], 
    [Limit1].[DrawingNo] AS [DrawingNo], 
    [Limit1].[MachineId] AS [MachineId], 
    [Limit1].[WorkItemState_Id] AS [WorkItemState_Id], 
    [Limit1].[ItemType_Id] AS [ItemType_Id], 
    [Limit1].[Repaired] AS [Repaired], 
    [Limit1].[MachineCycle] AS [MachineCycle], 
    [Limit1].[FirstSeenCheck] AS [FirstSeenCheck], 
    [Limit1].[LastSeen] AS [LastSeen], 
    [Limit1].[Archive] AS [Archive], 
    [Limit1].[CastingDateString] AS [CastingDateString], 
    [Limit1].[Deleted] AS [Deleted], 
    [Limit1].[DMC2] AS [DMC2], 
    [Limit1].[Id1] AS [Id1], 
    [Limit1].[WorkPlace_Id] AS [WorkPlace_Id], 
    [Limit1].[CastingFormIdent_Id] AS [CastingFormIdent_Id], 
    [Limit1].[FormIdentItemType_Id] AS [FormIdentItemType_Id]
    FROM ( SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[DMC] AS [DMC], 
        [Extent1].[FirstSeen] AS [F   </inputbuf>
  </process>
 </process-list>
 <resource-list>
  <pagelock fileid="1" pageid="531207" dbid="5" subresource="FULL" objectname="LP_R.dbo.WorkItems" id="lock6d7b2d800" mode="S" associatedObjectId="72057594043891712">
   <owner-list>
    <owner id="process66f184558" mode="S" />
   </owner-list>
   <waiter-list>
    <waiter id="process6472ad498" mode="IX" requestType="wait" />
   </waiter-list>
  </pagelock>
  <pagelock fileid="1" pageid="114492" dbid="5" subresource="FULL" objectname="LP_R.dbo.WorkItems" id="lock5cd1a2b00" mode="IX" associatedObjectId="72057594043891712">
   <owner-list>
    <owner id="process6472ad498" mode="IX" />
   </owner-list>
   <waiter-list>
    <waiter id="process66f184558" mode="S" requestType="wait" />
   </waiter-list>
  </pagelock>
 </resource-list>
</deadlock>

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Во-первых, поскольку в этом взаимоблокировке есть S-блокировка, рассмотрите возможность переключения базы данных на READ COMMITTED SNAPSHOT, чтобы ваши запросы SELECT использовали версионирование строк вместо S-блокировок для чтения базы данных. Это решит все S / X тупики и другие блокировки одним махом, но вам нужно будет проверить.

Во-вторых, для устранения этой тупиковой ситуации используйте транзакцию в вашей хранимой процедуре и получите большую блокировку заранее. Например, вместо блокировки IX вынудите его получить эксклюзивную блокировку таблицы с подсказкой TABLOCKX. Взаимные блокировки возникают только тогда, когда две сессии сначала получают совместимые блокировки, а затем позже пытаются получить несовместимые блокировки. Блокировки IX и S являются началом этого тупика, поэтому вы можете избежать его, убедившись, что хранимая процедура не получает маленького IX, и подождать, пока она не получит блокировку, которая позволит ей успешно завершиться.

0 голосов
/ 30 августа 2018

(как примечание)

delete c 
from dbo.charges c
inner join @chargeids t
  on t.id = c.id
where not exists(
  select 1 from dbo.chargeitems ci 
  where ci.Charge_id = c.id
)

Полагаю, на данном этапе уже нет chargeitems. И они, вероятно, были удалены непосредственно перед этим оператором удаления. Таким образом, транзакция процесса архивирования может быть намного длиннее, чем показано.

Итак, ваш процесс удаления удаляет определенные rows и устанавливает блокировку intent-X на страницах, в то время как процесс чтения сканирует pages, возможно, в другом порядке. FK s может установить еще несколько замков, если они есть.

TIPS

Посмотрите на фактический план выполнения оператора select. Он запрашивает TOP 1 без порядка, использует далеко не точный предикат where и присоединяется chargeitems к workitem_id (который, возможно, не имеет индекса). Исправление может помочь избавиться от scan (если есть) во время чтения. Может быть, вы попытаетесь выбрать top1 workitem и только после этого выберите top1 chargeitem для него.

Вы можете попытаться применить подсказку READPAST к оператору чтения (не будет ожидать заблокированных страниц) или повысить гранулярность операторов удаления до PAGLOCK, например. Попробуйте TABLOCK для процесса удаления, если он выполняется редко и подходит для этой системы.

UPD

На самом деле я пропустил главный момент: вы указали на удаление из charges, тогда как тупик включен WorkItem (как ясно показывает график тупиков). Но это не отменяет остальных моих предположений. Как показывает план выполнения, WorkItem действительно сканируется, в то время как это удаление выполняется в определенных строках:

delete from LP_R.dbo.workitems where id in (select id from @workitemIds); 

Вы можете применить подсказки из моего поста для выбора выписки и / или удаления выписки (все они в процессе архивации).

...