为什么选择查询需要IX锁定?

时间:2017-01-31 08:48:25

标签: sql-server database sql-server-2008-r2 deadlock

在检查下面的死锁图时,我发现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

这是计划:

enter image description here

以下是UPDATE查询:

update Table2
set [Status] = @0, Column5 = null, Column6 = @1
where ([Id] = @2)

这是计划:

enter image description here

这是死锁图:

<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为72057594073317376associatedObjectId)的对象为:[IX_Table2]

3 个答案:

答案 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
  • 持有
  • IXS模式不兼容。所以,DEADLOCK。
  • 并且,SELECT并未要求IX锁定

修复(暂时):