SQL Server死锁在同一个表上

时间:2011-12-02 07:06:04

标签: sql-server tsql deadlock

我们的应用程序中存在死锁情况问题。在过去的几天里,我已经阅读了很多关于阻塞,锁定和死锁的内容,试图了解问题以便解决它。

现在,当我读取有关死锁的错误日志信息时,我无法理解这种情况是如何存在的。看看这个(我已经重命名了表名,但重要的是在日志消息中名为OurTable的那个):

deadlock-list
deadlock victim=process1e2ac02c8
process-list
    process id=process1e2ac02c8 taskpriority=0 logused=0 waitresource=OBJECT: 11:290100074:0  waittime=704 ownerId=3144354890 transactionname=SELECT lasttranstarted=2011-12-01T14:43:20.577 XDES=0x80017920 lockMode=S schedulerid=6 kpid=7508 status=suspended spid=155 sbid=0 ecid=0 priority=0 trancount=0 lastbatchstarted=2011-12-01T14:43:20.577 lastbatchcompleted=2011-12-01T14:43:20.577 clientapp=.Net SqlClient Data Provider hostname=DE-1809 hostpid=5856 loginname=2Ezy isolationlevel=read committed (2) xactid=3144354890 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
     executionStack
      frame procname=.dbo.RetrieveSomething line=23 stmtstart=1398 stmtend=3724 sqlhandle=0x03000b0030d42d645a63e6006a9f00000100000000000000
         select
            Col1
            ,Col2
            ,(
                SELECT TOP(1)
                    Col1
                FROM
                    OurTable2 AS C
                        JOIN OurTable AS ETC ON C.Id = ETC.FKId
                            AND E.Id = C.FKId
                ORDER BY ETC.Col2
            ) AS Col3
        from OurTable3 AS E
    process id=process2df4894c8 taskpriority=0 logused=0 waitresource=OBJECT: 11:290100074:0  waittime=9713 ownerId=3144330250 transactionname=INSERT EXEC lasttranstarted=2011-12-01T14:43:11.573 XDES=0x370764930 lockMode=S schedulerid=13 kpid=4408 status=suspended spid=153 sbid=0 ecid=0 priority=0 trancount=1 lastbatchstarted=2011-12-01T14:43:11.573 lastbatchcompleted=2011-12-01T14:43:11.573 clientapp=.Net SqlClient Data Provider hostname=DE-1809 hostpid=5856 loginname=2Ezy isolationlevel=read committed (2) xactid=3144330250 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
     executionStack
      frame procname=adhoc line=1 sqlhandle=0x02000000ba6cb42612240bdb19f7303e279a714276c04344
         select
            Col1
            , Col2
            , Col3
            , ISNULL(
                (select top(1)
                    E_SUB.Col1 + ' ' + E_SUB.Col2
                    from OurTable3 as E_SUB 
                        inner join OurTable2 as C on E_SUB.Id = C.FKId
                        inner join OurTable as ETC on C.Id = ETC.FKId
                as Col3
        from OurTable4
            inner join dbo.OurTable as ETC on Id = ETC.FKId  
    process id=process8674c8 taskpriority=0 logused=0 waitresource=OBJECT: 11:290100074:5  waittime=338 ownerId=3143936820 transactionname=INSERT lasttranstarted=2011-12-01T14:38:24.423 XDES=0x1ecd229f0 lockMode=X schedulerid=7 kpid=12092 status=suspended spid=124 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2011-12-01T14:38:23.027 lastbatchcompleted=2011-12-01T14:38:23.013 clientapp=.Net SqlClient Data Provider hostname=DE-1809 hostpid=5856 loginname=2Ezy isolationlevel=read committed (2) xactid=3143936820 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
     executionStack
      frame procname=.dbo.UpsertSomething line=332 stmtstart=27712 stmtend=31692 sqlhandle=0x03000b00bbf2a93c0f63a700759f00000100000000000000
            insert into dbo.OurTable
            (
                Col1
                ,Col2
                ,Col3
            )
            values
            (
                @Col1
                ,@Col2
                ,@Col3
            )
       resource-list
        objectlock lockPartition=0 objid=290100074 subresource=FULL dbid=11 objectname=dbo.OurTable id=lock16a1fde80 mode=X associatedObjectId=290100074
         owner-list
         waiter-list
          waiter id=process1e2ac02c8 mode=S requestType=wait
        objectlock lockPartition=0 objid=290100074 subresource=FULL dbid=11 objectname=dbo.OurTable id=lock16a1fde80 mode=X associatedObjectId=290100074
         owner-list
          owner id=process8674c8 mode=X
         waiter-list
          waiter id=process2df4894c8 mode=S requestType=wait
        objectlock lockPartition=5 objid=290100074 subresource=FULL dbid=11 objectname=dbo.OurTable id=lock212f0f300 mode=IS associatedObjectId=290100074
         owner-list
          owner id=process1e2ac02c8 mode=IS
         waiter-list
          waiter id=process8674c8 mode=X requestType=wait

我读到的方式是:

spid 155正在等待OurTable上的共享表锁     (spid 124持有冲突的X锁)

spid 153正在等待OurTable上的共享表锁     (spid 124持有冲突的X锁)

spid 124正在等待OurTable上的Exclusive表锁定     (spid 155持有冲突的IS锁)

我的问题是如何发生这种情况。两个会话同时在整个表上保持一个锁。我认为通常的死锁是两个或更多的会话锁定不同的资源并等待彼此。但是这里的锁是在同一个资源上。它不是对索引的锁定,而是在桌面上。这个错误在我们的应用程序中很常见,并且一些锁必须是第一个请求的,如果整个表上已经锁定,为什么接受第二个锁?

任何能够暗示可能出现什么问题的人或任何遇到过类似僵局的人?

3 个答案:

答案 0 :(得分:3)

经过一番搜索和测试后,我非常有信心能够对自己的问题给出正确答案。

我要感谢马丁史密斯,他指出等待资源不同,让我朝着正确的方向前进。

正如马丁在评论中写道,等待资源是:11:290100074:0和11:290100074:5。 在搜索之后,事实证明,如果您在具有16个CPU或更多CPU的计算机上运行Sql Server R2,则Sql Server可以使用名为lock partitioning的功能。

本文除其他外说:

  

仅在单个上获取NL,SCH-S,IS,IU和IX锁定模式   分区。

在我的情况下,spid 155会在行或页面上放置一个共享锁,因此会在对象上放置一个预期的共享锁,并且使用锁分区功能,这恰好在分区ID 5上。

同时spid 124需要使用独占锁来锁定整个对象,因此需要将X锁定在所有分区上。

  

除NL之外的模式中的共享(S),独占(X)和其他锁定,   必须在所有分区上获取SCH-S,IS,IU和IX   分区ID 0以及分区ID顺序中的以下内容。

当它到达分区ID 5时,它被告知spid 155拥有一个IS锁,它需要等到该锁被释放。

现在,当spid 124正在等待释放的IS锁定lock escalation发生在spid 155上时,它会请求表上的共享锁。这意味着它需要将S锁定在从id 0开始的所有分区上。但是在id 0上它立即触及墙壁,因为spid 124已经在该分区上拥有独占锁定。你有僵局的原因。

我不能保证100%这是确切的答案,但我很确定我是,如果不是100%正确,至少接近答案。

解决方案?好。锁分区功能无法关闭,但另一方面,您可以使用不同的事务级别控制lock escalation,并在alter table语句中控制不同的选项。

我将继续调查查询强制锁定升级的原因,因为我认为在我的特定情况下解决方案是以某种方式调整查询以不升级。至少我会在使用上述工具之前尝试这个。

希望这个答案有助于其他类似的问题。

答案 1 :(得分:2)

“通常的死锁是指当两个或多个会话锁定不同的资源并等待彼此时”并非总是如此“ - 也存在转换死锁。即使两个进程只在一个资源上竞争,它们仍然可以接受转换死锁which I described here.

此外,虽然最着名的死锁场景涉及两个连接以不同的顺序修改两个表,但还有其他死锁场景只涉及一个表。此外,在某些情况下,每个连接只需要发出一个语句,这足以导致死锁。此外,在某些情况下,只有一个连接需要修改或获取排他锁 - the other one may only read data and only acquire shared locks and still embrace in a deadlock.

还有一件事:回答“没有任何查询在事务中运行”注释 - 每个DML语句总是在事务中运行,而DML也意味着选择。死锁中涉及的所有命令都在事务的上下文中运行。按照第二个链接,运行repro脚本 - 您将亲自查看。

无论如何,我只是在快照隔离下运行select - 这会阻止这种特定的死锁(当一个连接只读取时)发生。

答案 2 :(得分:0)

之所以会发生这种情况,是因为您的锁定策略过于简单,而且遇到了这种情况会让您受伤。

如下所示:如果你选择了一个足够糟糕的锁定级别,你会得到一个读锁定,并且当另一个读锁定到位时无法更新到写锁定,如果两个应用程序这样做...你可以有完全的行为(进程1读取ock,2个读取锁定,1个要更新写入锁定(等待),2个要更新写入锁定 - 死锁。

在你的特定情况下,读取似乎提出了读锁定,而upsert确实让一个人决定更新,但是然后打击插入所需的锁定(是的,你可以有一个阻止一个锁定的锁定插入)。

  

我们的应用程序中经常出现此错误,并且必须首先锁定一些锁定   如果整个桌子上已经锁定,为什么接受第二个锁?

初学者设计问题。发生此问题是因为某些锁是共享的(主要是读锁),允许建立其他读锁。如果你允许的话。我建议确保不会发生死锁。要么读取不要保留锁定(使用NOLOCK),要么在更早的时候获得正确的写锁定。