为什么 select 查询需要 IX 锁?
Why does a select query require an IX lock?
在检查下面的死锁图时,我发现一个 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]
只要不使用脏读,select 总是需要锁。
更新查询更改了索引。您真的希望 select 查询甚至 都不会注意到 索引已更改吗?您偶尔会从查询中得到随机的废话(事实上,正是在您遇到的情况下 - 而不是死锁,您会得到格式错误的数据)。
当然,select 通常不会采用独占锁 - 即使在这种情况下,您也可以看到锁是共享的,而不是独占的。但这仍然意味着任何想要 write 到该数据的人都不能。 update 语句需要做到这一点 - 同时持有索引的独占锁,select 需要完成。
通常无法避免死锁。它们是您应该做好准备的预期行为 - 您的应用程序中的典型响应应该是在您收到 1205 错误时重复该事务。这是性能和便利性之间的折衷,不会危及正确性。出现错误的进程是随机选择的,因此您在读取和写入时都需要它。
在您的情况下,很明显您正在更改聚簇索引 - 这通常是个坏主意,并且会导致很多死锁机会(毕竟,您 至少部分重建 table。考虑更改您的索引,使其更符合您的应用程序实际执行的读写操作。如果它经常发生,它也可能对性能不利。
编辑:实际上,锁似乎在 IX_Table2
的两页上 - 原来是钥匙的那一页,以及更改后需要的那一页。这两个锁是按顺序获取的,并且与 select 的顺序不同。给定索引的布局,这将相对频繁地发生——因为两个语句都处理为空的 Column5
。我不认为在这种情况下它真的是可以避免的——也许你可以稍微调整一下索引布局,但只有当死锁造成实际问题时才有意义——如果你每天只损失几秒钟或更少,它很可能被浪费了努力并可能带来负面影响 side-effects.
有关分析和解决 MS SQL 死锁的更多信息,请尝试 How to resolve a deadlock。如果您需要更多信息来解决您的问题,请咨询您的 DBA,并考虑在 DBA Stack Exchange 上发布问题 - 确保包含所有必要的信息,至少包括所涉及的 table 的 DDL,包括指数。使用 sp_help
将死锁报告中的对象 ID 转换为 DDL 中的实际名称。
如果仔细观察图表,您会发现:
reader,进程:process569f048 在页面:3017343 上有共享锁,正在等待对象 72057594073317376 的页面:3017144 上的共享锁
更新进程:process8ee3dc8 在页面 3017144 上有 IX 锁,正在等待对象 72057594073317376 的 3017343 上的 IX 锁。
这就是死锁所在。
要查找引用的对象,您可以使用从 stack overflow answer here 中收集的以下信息
对象id指的是hobts(Heap Or Binary Tree),在sys.partitions.
中找到
在数据库 5 中尝试以下查询,您将找到受影响的对象和索引。
SELECT hobt_id, object_name(p.[object_id]), index_id
FROM sys.partitions p
WHERE hobt_id = 72057594073317376
正如我在评论中指出的那样,在 table 具有聚簇索引的情况下,所有 non-clustered 索引都将聚簇键作为索引的一部分,因此需要在以下情况下进行更新集群键的更新。
我怀疑这个对象将是需要更新的二级索引,可能是因为它是最后一页。
这是我对死锁图的错误解释。 wait-resource
字段清楚地表明:
SELECT
进程在 IX_Table2
的 PAGE 上持有 S
锁:3017343
UPDATE
进程在 IX_Table2
的 PAGE 上持有 IX
锁:
3017144
SELECT
需要带 S
锁的页面 3017144;但它由 UPDATE
持有
UPDATE
需要带 IX
锁的页面 3017343;但它由 SELECT
持有
IX
和 S
模式不兼容。所以,陷入僵局。
- 而且,
SELECT
没有要求 IX
锁
修复(暂时):
- Re-run
SELECT
- Use retries in the SP 包含
SELECT
SET DEADLOCK_PRIORITY LOW;
在 SP
在检查下面的死锁图时,我发现一个 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]
只要不使用脏读,select 总是需要锁。
更新查询更改了索引。您真的希望 select 查询甚至 都不会注意到 索引已更改吗?您偶尔会从查询中得到随机的废话(事实上,正是在您遇到的情况下 - 而不是死锁,您会得到格式错误的数据)。
当然,select 通常不会采用独占锁 - 即使在这种情况下,您也可以看到锁是共享的,而不是独占的。但这仍然意味着任何想要 write 到该数据的人都不能。 update 语句需要做到这一点 - 同时持有索引的独占锁,select 需要完成。
通常无法避免死锁。它们是您应该做好准备的预期行为 - 您的应用程序中的典型响应应该是在您收到 1205 错误时重复该事务。这是性能和便利性之间的折衷,不会危及正确性。出现错误的进程是随机选择的,因此您在读取和写入时都需要它。
在您的情况下,很明显您正在更改聚簇索引 - 这通常是个坏主意,并且会导致很多死锁机会(毕竟,您 至少部分重建 table。考虑更改您的索引,使其更符合您的应用程序实际执行的读写操作。如果它经常发生,它也可能对性能不利。
编辑:实际上,锁似乎在 IX_Table2
的两页上 - 原来是钥匙的那一页,以及更改后需要的那一页。这两个锁是按顺序获取的,并且与 select 的顺序不同。给定索引的布局,这将相对频繁地发生——因为两个语句都处理为空的 Column5
。我不认为在这种情况下它真的是可以避免的——也许你可以稍微调整一下索引布局,但只有当死锁造成实际问题时才有意义——如果你每天只损失几秒钟或更少,它很可能被浪费了努力并可能带来负面影响 side-effects.
有关分析和解决 MS SQL 死锁的更多信息,请尝试 How to resolve a deadlock。如果您需要更多信息来解决您的问题,请咨询您的 DBA,并考虑在 DBA Stack Exchange 上发布问题 - 确保包含所有必要的信息,至少包括所涉及的 table 的 DDL,包括指数。使用 sp_help
将死锁报告中的对象 ID 转换为 DDL 中的实际名称。
如果仔细观察图表,您会发现:
reader,进程:process569f048 在页面:3017343 上有共享锁,正在等待对象 72057594073317376 的页面:3017144 上的共享锁
更新进程:process8ee3dc8 在页面 3017144 上有 IX 锁,正在等待对象 72057594073317376 的 3017343 上的 IX 锁。
这就是死锁所在。
要查找引用的对象,您可以使用从 stack overflow answer here 中收集的以下信息 对象id指的是hobts(Heap Or Binary Tree),在sys.partitions.
中找到在数据库 5 中尝试以下查询,您将找到受影响的对象和索引。
SELECT hobt_id, object_name(p.[object_id]), index_id
FROM sys.partitions p
WHERE hobt_id = 72057594073317376
正如我在评论中指出的那样,在 table 具有聚簇索引的情况下,所有 non-clustered 索引都将聚簇键作为索引的一部分,因此需要在以下情况下进行更新集群键的更新。 我怀疑这个对象将是需要更新的二级索引,可能是因为它是最后一页。
这是我对死锁图的错误解释。 wait-resource
字段清楚地表明:
SELECT
进程在IX_Table2
的 PAGE 上持有S
锁:3017343UPDATE
进程在IX_Table2
的 PAGE 上持有IX
锁: 3017144SELECT
需要带S
锁的页面 3017144;但它由UPDATE
持有
UPDATE
需要带IX
锁的页面 3017343;但它由SELECT
持有
IX
和S
模式不兼容。所以,陷入僵局。- 而且,
SELECT
没有要求IX
锁
修复(暂时):
- Re-run
SELECT
- Use retries in the SP 包含
SELECT
SET DEADLOCK_PRIORITY LOW;
在 SP