在检查下面的死锁图时,我发现SELECT
查询(仅在第一个进程process569f048
执行的SP内部查询)和UPDATE
查询形成死锁;并且SELECT
查询需要IX
锁定。
在什么情况下SELECT
需要这样的锁?我该怎么做才能避免僵局?
以下是SELECT
查询:
SELECT TOP (@p_takeCount)
t.Id
,s.Column2
,t.STATUS
,t.Column3
,t.Column4
FROM Table2 t WITH (INDEX (IX_Table2))
INNER JOIN Table1 s ON s.Id = t.ParentId
WHERE t.STATUS != 0
AND t.Column5 IS NULL
AND s.SomeId = @p_someId
AND s.Category = 2
ORDER BY t.id
这是计划:
以下是UPDATE
查询:
update Table2
set [Status] = @0, Column5 = null, Column6 = @1
where ([Id] = @2)
这是计划:
这是死锁图:
<deadlock>
<victim-list>
<victimProcess id="process569f048" />
</victim-list>
<process-list>
<process id="process569f048" taskpriority="0" logused="0" waitresource="PAGE: 5:1:3017144" waittime="2867" ownerId="964271246" transactionname="SELECT" lasttranstarted="2017-01-29T10:10:49.643" XDES="0x800f9d20" lockMode="S" schedulerid="10" kpid="10108" status="suspended" spid="70" sbid="2" ecid="2" priority="0" trancount="0" lastbatchstarted="2017-01-29T10:10:49.643" lastbatchcompleted="2017-01-29T10:10:49.643" clientapp="EntityFramework" hostname="LOCALHOST" hostpid="4936" isolationlevel="read committed (2)" xactid="964271246" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="" line="17" stmtstart="1298" stmtend="1954" sqlhandle="0x03000500d21f5e3dd6d19700cca400000100000000000000" />
</executionStack>
<inputbuf />
</process>
<process id="process8ee3dc8" taskpriority="0" logused="17956" waitresource="PAGE: 5:1:3017343" waittime="2864" ownerId="964271345" transactionname="user_transaction" lasttranstarted="2017-01-29T10:10:49.667" XDES="0xafdbb03b0" lockMode="IX" schedulerid="17" kpid="9468" status="suspended" spid="61" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2017-01-29T10:10:49.703" lastbatchcompleted="2017-01-29T10:10:49.703" clientapp="EntityFramework" hostname="LOCALHOST" hostpid="20696" loginname="dbuser_d" isolationlevel="read committed (2)" xactid="964271345" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="" line="1" stmtstart="74" sqlhandle="0x02000000403aaa03bd8879de1c73d49641f1f81b6ca095af" />
<frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000" />
</executionStack>
<inputbuf>
(@0 tinyint,@1 varchar(64),@2 bigint)update [dbo].[Table2]
set [Status] = @0, [Column5] = null, [Column6] = @1
where ([Id] = @2)
</inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="3017144" dbid="5" objectname="" id="lockc296c6380" mode="IX" associatedObjectId="72057594073317376">
<owner-list>
<owner id="process8ee3dc8" mode="IX" />
</owner-list>
<waiter-list>
<waiter id="process569f048" mode="S" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="3017343" dbid="5" objectname="" id="lockd33965a80" mode="S" associatedObjectId="72057594073317376">
<owner-list>
<owner id="process569f048" mode="S" />
</owner-list>
<waiter-list>
<waiter id="process8ee3dc8" mode="IX" requestType="wait" />
</waiter-list>
</pagelock>
</resource-list>
</deadlock>
索引详情:
[PK_Table2] PRIMARY KEY CLUSTERED ([Id] ASC);
[IX_Table2]([Column5] ASC, [Status] ASC) INCLUDE ( [Id],[ParentId],[Column3],[Column4]) WHERE ([Column5] IS NULL);
ID为72057594073317376
(associatedObjectId
)的对象为:[IX_Table2]
答案 0 :(得分:2)
只要您不使用脏读,就必须使用锁定。
更新查询会更改索引。您是否真的希望选择查询甚至通知索引已更改?你偶尔会从你的查询中得到随机的废话(实际上,正好在你在这里的场景中 - 而不是死锁,你会得到格式错误的数据)。
当然,选择通常不会采用独占锁定 - 即使在这种情况下,您也可以看到锁是共享的,而不是独占的。但这仍然意味着任何想要写数据的人都无法做到。并且update语句需要做到这一点 - 同时在索引上保持一个独占锁,select需要完成。
一般无法避免死锁。它们是您应该准备的预期行为 - 应用程序中的典型响应应该是在您收到1205错误时重复该事务。这是性能和便利之间的折衷,不会危及正确性。获取错误的过程是随机选择的,因此在读取和写入时都需要这样做。
在你的情况下,显而易见的是,你正在改变聚集索引 - 这通常是一个坏主意,并导致大量死锁机会(毕竟,你&# 39;重新重建表,至少部分地)。考虑将索引更改为更符合应用程序实际执行的读写操作。如果经常发生,它也可能对性能不利。
编辑:实际上,似乎锁是在IX_Table2
的两个页面上 - 一个是以前的密钥,以及变更之后需要的密钥。两个锁按顺序进行,顺序与select不同。给定索引的布局,这将经常发生 - 因为两个语句都处理Column5
为空。在这种情况下,我不认为它真的可以避免 - 也许你可以稍微调整索引布局,但这真的只有在死锁造成实际问题时才有意义 - 如果你每天只丢几秒钟或者更少,它可能会浪费精力并且可能产生负面副作用。
有关分析和解决MS SQL死锁的更多信息,请尝试How to resolve a deadlock。如果您需要更多信息来解决问题,请咨询您的DBA,并考虑在DBA Stack Exchange上发布问题 - 确保包含所有必要的信息,包括至少所涉及的表的DDL,包括索引。使用sp_help
将死锁报告中的对象ID转换为DDL中的实际名称。
答案 1 :(得分:2)
如果仔细查看图表,可以看到:
读者,进程:process569f048页面上有一个共享锁:3017343并正在等待页面上的共享锁:对象72077594073317376的3017144
更新过程:process8ee3dc8页面上有一个IX锁定:3017144正在等待对象72057594073317376的3017343上的IX锁定。
这就是死锁的地方。
要查找引用的对象,您可以使用以下从stack overflow answer here收集的信息 对象id指的是在sys.partitions中找到的hobts(堆或二叉树)。
在数据库5中尝试以下查询,您将找到哪个对象和哪个索引受到影响。
SELECT hobt_id, object_name(p.[object_id]), index_id
FROM sys.partitions p
WHERE hobt_id = 72057594073317376
正如我在评论中指出的那样,如果表具有聚簇索引,则所有非聚簇索引都将聚簇键作为索引的一部分,因此在更新聚簇键时需要更新。 我怀疑这个对象将是需要由更新更新的二级索引,可能是因为它是最后一页。
答案 2 :(得分:0)
这是我对死锁图的错误解释。 wait-resource
字段清楚地表明:
SELECT
进程对S
的 PAGE 持有IX_Table2
锁定:3017343 UPDATE
进程对IX
的 PAGE 进行IX_Table2
锁定:
3017144 SELECT
需要使用S
锁定的页面3017144;但它由UPDATE
UPDATE
需要使用IX
锁定的页面3017343;但它由SELECT
IX
和S
模式不兼容。所以,DEADLOCK。SELECT
并未要求IX
锁定修复(暂时):
SELECT
SELECT
SET DEADLOCK_PRIORITY LOW;