【问题标题】:SQL Server Deadlock by IX pagelockIX pagelock 导致的 SQL Server 死锁
【发布时间】:2018-08-30 10:20:21
【问题描述】:

我遇到了一个僵局,我不知道如何解决它。我已经修复了一些来自 EF 端的其他查询,这些查询导致了相同的死锁(在 sp 中的同一行),但是这个不能修改,这是一个非常基本的查询,我认为必须有一种更简单的方法来重写 SP 或者修改索引以避免页面锁定。

三个表:

  • 工作项(ID、DMC、...)
  • 费用(ID,...)
  • ChargeItems(Id, Charge_Id, Workitem_Id, ...)

两个进程:

  • 多次调用的 EF 查询。 (~5-10/分钟)
  • 一种存储过程,它通过将存档移动到其他数据库并从源中删除它们来存档数据。

僵局:

当 SP 尝试删除空 Charges 时,它在最后一步抛出,其中 ChargeItems 没有记录。至此,它已经删除了所有 ChargeItems,只需要删除空的费用和工作项。

EF 运行的查询正在通过其 DMC 搜索 Workitem,而 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]

select的执行计划:

SP的一部分:

;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>

【问题讨论】:

  • 存档过程中是否需要物理删除行?您可以将该行标记为已归档,并在下班时间删除这些行吗?
  • @Mike 行已被标记,此过程正在删除标记的行。不幸的是,没有休息时间。他们正在生产 24/7。

标签: sql-server entity-framework deadlock


【解决方案1】:

(作为旁注)

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 并在页面上放置一个意图 X 锁定,而读取过程正在扫描 pages,可能以不同的顺序。 FKs 可以放置更多的锁(如果有的话)。

提示

看看 select 语句的实际执行计划。它请求TOP 1 没有顺序,使用远不精确的where 谓词并在workitem_id 上加入chargeitems(可能没有索引)。修复它可以帮助在阅读时摆脱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); 

您可以将我帖子中的提示应用到选择语句和/或删除语句(所有这些都在归档过程中)。

【讨论】:

  • 我已经添加了select语句的执行计划。 sp的这一点上没有充电物品,是的,sp要长得多。看来您说这不能通过仅修改 SP 来避免?修改索引的属性怎么样?
  • 谢谢,使用WITH TABLOCK 解决了我的问题。从那以后尝试了几次,没有任何死锁!
【解决方案2】:

首先,由于这个死锁中存在 S 锁,请考虑将数据库切换到 READ COMMITTED SNAPSHOT,以便您的 SELECT 查询将使用行版本控制而不是 S 锁来读取数据库。这将一举解决所有 S/X 死锁和其他阻塞,但您需要进行测试。

其次,要解决此死锁,请在存储过程中使用事务并尽早获得大锁。例如,代替 IX 锁强制它使用 TABLOCKX 提示获取排他表锁。只有当两个会话首先获得兼容的锁,然后稍后尝试获得不兼容的锁时,才会发生死锁。 IX 和 S 锁是这个死锁的开始,因此您可以通过确保存储过程不会获得微不足道的 IX 来阻止它,并等待它获得能够使其成功完成的锁。

【讨论】:

  • 谢谢大卫,我必须按照你和 Ivan 的建议添加 TABLOCK 提示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多