SQL 服务器 - 密钥更新死锁
SQL Server - Deadlock on key update
SQL 服务器 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
所以我有两个简单的 tables,在 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 的第二次更新。
这会导致死锁。我正在 运行ning 快照隔离级别 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>
当我查看锁获取时,我发现已完成以下锁
- 获得 - IX - 对象
- 获得 - IX - PAGE
- 获得 - X - KEY
- 获得 - X - EXTENT
- 已发布 - X - EXTENT
- 获得 - U - EXTENT
- 获得 - X - PAGE
- 已发布 - U - EXTENT
- 已发布 - X - 页
- 已发布 - 0 - KEY
- 已发布 - 0 - 页
所以,一切都被释放了,除了从一开始的 OBJECT,它似乎是主键索引。我想它将一直保留到事务提交完成并且不会立即释放。这似乎导致了僵局。
你能回答我以下问题吗:
- 我是否正确认为聚集主键索引锁将一直保留到提交?
- 我是否正确认为这将阻止所有其他并发更新尝试等待?
- 如果是这样,为什么在 where 子句中使用给定主键更新时整个索引被锁定?这意味着主键 where 子句的每次更新都将锁定事务的整个 table。我简直不敢相信。
- 在 fooval 和 barval 上添加索引是最好的解决方案吗?
- sql 服务器的行为是否与 sql 服务器 express 不同?
交叉更新会导致死锁。无论索引、索引类型等如何。始终尝试以相同的顺序更新表。话虽如此,无论索引如何,如果数据在同一页上,那么您就会遇到锁定情况,并且因为您正在以交叉方式更新,所以您的命令之一将被选为死锁。
1.Yes
2.Yes
3.This回答起来比较复杂,网上有很多精彩的解释,但是你要明白的是,无论索引如何,锁都会发生,而且经常发生,但死锁是由于策略不当造成的。
4.Irrelevant
5.Yes 在某些情况下但不适用于这种情况。
SQL 服务器 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
所以我有两个简单的 tables,在 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 的第二次更新。
这会导致死锁。我正在 运行ning 快照隔离级别 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>
当我查看锁获取时,我发现已完成以下锁
- 获得 - IX - 对象
- 获得 - IX - PAGE
- 获得 - X - KEY
- 获得 - X - EXTENT
- 已发布 - X - EXTENT
- 获得 - U - EXTENT
- 获得 - X - PAGE
- 已发布 - U - EXTENT
- 已发布 - X - 页
- 已发布 - 0 - KEY
- 已发布 - 0 - 页
所以,一切都被释放了,除了从一开始的 OBJECT,它似乎是主键索引。我想它将一直保留到事务提交完成并且不会立即释放。这似乎导致了僵局。
你能回答我以下问题吗:
- 我是否正确认为聚集主键索引锁将一直保留到提交?
- 我是否正确认为这将阻止所有其他并发更新尝试等待?
- 如果是这样,为什么在 where 子句中使用给定主键更新时整个索引被锁定?这意味着主键 where 子句的每次更新都将锁定事务的整个 table。我简直不敢相信。
- 在 fooval 和 barval 上添加索引是最好的解决方案吗?
- sql 服务器的行为是否与 sql 服务器 express 不同?
交叉更新会导致死锁。无论索引、索引类型等如何。始终尝试以相同的顺序更新表。话虽如此,无论索引如何,如果数据在同一页上,那么您就会遇到锁定情况,并且因为您正在以交叉方式更新,所以您的命令之一将被选为死锁。
1.Yes
2.Yes
3.This回答起来比较复杂,网上有很多精彩的解释,但是你要明白的是,无论索引如何,锁都会发生,而且经常发生,但死锁是由于策略不当造成的。
4.Irrelevant
5.Yes 在某些情况下但不适用于这种情况。