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

mar*_*s-O 14 sql-server locking blocking

我在 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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

USE [LockingTestDB]
GO

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

USE [master]
GO
Run Code Online (Sandbox Code Playgroud)

您会注意到第二个查询将被第一个查询阻止。停止第一个查询并执行 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
Run Code Online (Sandbox Code Playgroud)

又为什么?

Pau*_*ite 21

正如Books Online 中所述UPDLOCK获取更新锁并将它们保留到事务结束。

如果没有用于定位要锁定的行的索引,则所有测试行都将被锁定,并且对符合条件的行的锁定将一直保持到事务完成为止。

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

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

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

相关信息:Read Committed Snapshot Isolation 下的数据修改