为什么UPDLOCK导致SELECT挂起(锁定)?


13

我在SQL SERVER中有一个选择可以锁定整个表。

这是设置脚本(请确保您不覆盖任何内容)

USE [master]
GO

IF EXISTS(SELECT 1 FROM sys.databases d WHERE d.name = 'LockingTestDB')
DROP DATABASE LockingTestDB
GO

CREATE DATABASE LockingTestDB
GO

USE [LockingTestDB]
GO
IF EXISTS(SELECT 1 FROM sys.tables t WHERE t.name = 'LockingTestTable')
  DROP TABLE LockingTestTable
GO

CREATE TABLE LockingTestTable (
  Id int IDENTITY(1, 1),
  Name varchar(100),
  PRIMARY KEY CLUSTERED (Id)
)
GO

INSERT INTO LockingTestTable(Name) VALUES ('1')
INSERT INTO LockingTestTable(Name) VALUES ('2')
GO

打开一个新的查询窗口并运行以下事务(其中有一个等待):

USE [LockingTestDB]
GO

BEGIN TRANSACTION
  SELECT * FROM LockingTestTable t WITH (UPDLOCK, ROWLOCK) WHERE t.Name = '1'
  WAITFOR DELAY '00:01:00'

COMMIT TRANSACTION
--ROLLBACK
GO

USE [master]
GO

另一个将运行(确保它们同时运行):

USE [LockingTestDB]
GO

SELECT * FROM LockingTestTable t WITH (UPDLOCK, ROWLOCK) WHERE t.Name = '2'

USE [master]
GO

您会注意到第二个查询将被第一个查询阻止。停止第一个查询并执行ROLLBACK,第二个查询将完成。

为什么会这样呢?

PS:在Name上添加非聚集索引(具有完整覆盖范围)将解决此问题:

USE [LockingTestDB]
GO

CREATE NONCLUSTERED INDEX [IX_Name] ON [dbo].[LockingTestTable] 
(
  [Name] ASC
)
INCLUDE ( [Id]) WITH (STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

再次为什么?

Answers:


19

联机丛书中所述UPDLOCK获取更新锁并将其保存到事务结束。

如果没有索引来定位要锁定的行,则所有被测试的行都将被锁定,并且对合格行的锁定将被保留,直到事务完成。

第一个事务在name = 1的行上拥有一个更新锁。第二个事务在尝试获取同一行的更新锁时被阻塞(以测试该行的name = 2)。

使用索引,SQL Server可以快速定位和锁定仅那些符合条件的行,因此不会发生冲突。

您应该与合格的数据库专业人员一起检查代码,以验证锁定提示的原因,并确保存在适当的索引。

相关信息:读取提交的快照隔离下的数据修改

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.