SQL Server-密钥更新时出现死锁

SQL Server-密钥更新时出现死锁,sql,sql-server,multithreading,locking,deadlock,Sql,Sql Server,Multithreading,Locking,Deadlock,SQL Server 2014 Express 我已将我的问题简化为以下几点: CREATE TABLE [dbo].[foo]( [fooid] [numeric](10, 0) IDENTITY(1,1) NOT NULL, [fooval] [nvarchar](4), CONSTRAINT [foo_PK] PRIMARY KEY CLUSTERED ( [fooid] ASC )WITH (PAD_INDEX = OFF, ST

SQL Server 2014 Express

我已将我的问题简化为以下几点:

CREATE TABLE [dbo].[foo](
    [fooid] [numeric](10, 0) IDENTITY(1,1) NOT NULL,
    [fooval] [nvarchar](4),
    CONSTRAINT [foo_PK] PRIMARY KEY CLUSTERED 
    (
        [fooid] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,  ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

INSERT INTO [dbo].[foo] ([fooval]) VALUES (1) 
GO

INSERT INTO [dbo].[foo] ([fooval]) VALUES (2)
GO

CREATE TABLE [dbo].[bar](
    [barid] [numeric](10, 0) IDENTITY(1,1) NOT NULL,
    [barval] [nvarchar](4),
    CONSTRAINT [bar_PK] PRIMARY KEY CLUSTERED 
    (
        [barid] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

INSERT INTO [dbo].[bar] ([barval]) VALUES (1)
GO

INSERT INTO [dbo].[bar] ([barval]) VALUES (2)
GO
因此,我有两个简单的表,在fooid和barid上有一个集群主键

我在两个调试器中运行以下两个查询

第一个问题:

BEGIN TRAN
UPDATE dbo.foo SET fooval = 1 WHERE fooid = 1

UPDATE dbo.bar SET barval = 1 WHERE barval = 1
COMMIT
第二个问题:

BEGIN TRAN
UPDATE dbo.bar SET barval = 2 WHERE barid = 2

UPDATE dbo.foo SET fooval = 2 WHERE fooval = 2
COMMIT
在调试期间,我执行查询1的第一次更新,然后执行查询2的第一次更新,然后执行查询1的第二次更新,最后执行查询2的第二次更新

这将导致死锁。我正在运行快照隔离级别Read Committed

图表显示:

<deadlock-list>
 <deadlock victim="process2f3ed64e8">
  <process-list>
   <process id="process2f3ed64e8" taskpriority="0" logused="288" waitresource="KEY: 5:72057607973896192 (227b7397de24)" waittime="2067" ownerId="1978563" transactionname="user_transaction" lasttranstarted="2015-08-24T16:24:57.280" XDES="0x2e2ff23b0" lockMode="U" schedulerid="1" kpid="9892" status="suspended" spid="59" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-08-24T16:24:56.997" lastbatchcompleted="2015-08-24T16:24:56.993" lastattention="1900-01-01T00:00:00.993" clientapp="Microsoft SQL Server Management Studio - Abfrage" hostname="VSL53439" hostpid="9124" loginname="x" isolationlevel="read committed (2)" xactid="1978563" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="6" stmtstart="38" stmtend="146" sqlhandle="0x02000000118b7210fc35334336b07155dea42e1470abe8dd0000000000000000000000000000000000000000">
unknown     </frame>
     <frame procname="adhoc" line="6" stmtstart="336" stmtend="426" sqlhandle="0x02000000bf0a381fd6fec29b6ed330f87409b4e8c47d26f10000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
BEGIN TRAN
UPDATE dbo.bar SET barval = 2 WHERE barid = 2

UPDATE dbo.foo SET fooval = 2 WHERE fooval = 2
COMMIT    </inputbuf>
   </process>
   <process id="process2e01b5088" taskpriority="0" logused="432" waitresource="KEY: 5:72057607973830656 (c939eba47c7b)" waittime="2970" ownerId="1978502" transactionname="user_transaction" lasttranstarted="2015-08-24T16:24:54.100" XDES="0x2df783000" lockMode="U" schedulerid="5" kpid="1928" status="suspended" spid="53" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-08-24T16:24:53.730" lastbatchcompleted="2015-08-24T16:24:53.730" lastattention="1900-01-01T00:00:00.730" clientapp="Microsoft SQL Server Management Studio - Abfrage" hostname="VSL53439" hostpid="4348" loginname="x" isolationlevel="read committed (2)" xactid="1978502" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="6" stmtstart="38" stmtend="146" sqlhandle="0x02000000f8c0c134764c79fe77f7cda514cc62eaf1a50cc80000000000000000000000000000000000000000">
unknown     </frame>
     <frame procname="adhoc" line="6" stmtstart="336" stmtend="426" sqlhandle="0x020000005c75f728d068a9d6386669fb7b8e315b3e484d640000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
BEGIN TRAN
UPDATE dbo.foo SET fooval = 1 WHERE fooid = 1

UPDATE dbo.bar SET barval = 1 WHERE barval = 1
COMMIT    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057607973896192" dbid="5" objectname="dbdevelop.dbo.foo" indexname="foo_PK" id="lock2ea279880" mode="X" associatedObjectId="72057607973896192">
    <owner-list>
     <owner id="process2e01b5088" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process2f3ed64e8" mode="U" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057607973830656" dbid="5" objectname="dbdevelop.dbo.bar" indexname="bar_PK" id="lock2eb0e6500" mode="X" associatedObjectId="72057607973830656">
    <owner-list>
     <owner id="process2f3ed64e8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process2e01b5088" mode="U" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

不为人知
不为人知
开始训练
更新dbo.bar集barval=2,其中barid=2
更新dbo.foo SET fooval=2,其中fooval=2
犯罪
不为人知
不为人知
开始训练
更新dbo.foo SET fooval=1,其中fooid=1
更新dbo.bar SET barval=1,其中barval=1
犯罪
当我查看锁时,我看到以下锁已经完成

  • 已获取-IX-对象
  • 第9页
  • 获得性X-键
  • 获得性X-范围
  • 已发布-X-范围
  • 获得性-U-范围
  • 获得性X-页面
  • 释放-U-范围
  • 已发布-X页
  • 释放-0-键
  • 已发布-0-页
  • 因此,所有内容都被释放,除了从一开始的对象,它似乎是主键索引。我猜它会一直保存到事务提交完成,而不是立即发布。这似乎导致了僵局

    你能回答我以下问题吗

  • 集群主键索引锁将保留到提交之前,这一点正确吗
  • 这将阻止所有其他并发更新尝试等待,对吗
  • 如果是这样,为什么在where子句中使用给定的主键更新时整个索引会被锁定?这意味着对主键where子句的每次更新都将锁定事务的整个表。真不敢相信
  • 在fooval和barval上添加索引的最佳解决方案是什么
  • sql server的行为是否与sql server express有所不同

  • 交叉更新会导致死锁。不考虑索引、索引类型等。。始终尝试以相同的顺序更新表。如上所述,不管索引如何,如果数据在同一页上,那么您就有一个锁定场景,因为您正在以交叉方式更新,您的一个命令将被选择为死锁

    1.是
    2.是
    3.这个问题回答起来很复杂,互联网上有很多精彩的解释,但你应该了解的是,无论索引如何,锁都会发生,而且经常发生,但死锁是由于策略不当造成的。
    4.无关

    5.在某些情况下是的,但在这种情况下不是。

    这是一个很大的问题。由于您使用两个调试器来运行单独的事务,我假设您同时运行它们?是的,如上所述:“在调试期间,我执行查询1的第一次更新,然后执行查询2的第一次更新,然后执行查询1的第二次更新,最后执行查询2的第二次更新。”如果您想避免死锁,可以尝试使用“while@@Trancount 0,开始打印‘Waiting’END”,然后打印您的事务?这将使它等到任何事务完成后再尝试运行另一个事务。但我不确定它是否适用。”