UPDLOCK和HOLDLOCK查询没有创建预期的锁

时间:2018-04-05 18:38:11

标签: sql-server database sql-server-2016

我有下表:

CREATE TABLE [dbo].[table1](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [name] [nvarchar](50) NULL,
 CONSTRAINT [PK_table1] PRIMARY KEY CLUSTERED 
(
    [id] 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

我正在学习SQL锁是如何工作的,我正在尝试测试我想要锁定行被读取和更新的情况。这个任务的一些灵感来自于这个article,而这里是我试图解决的original problem

当我运行这个T-SQL时:

BEGIN TRANSACTION

SELECT * FROM dbo.table1 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:15'

COMMIT TRANSACTION

我希望在表上放置一个独占锁,特别是对于行(如果我在主键上有一个WHERE语句)

但运行此查询,我可以看到GRANTed LOCK用于请求模式IX。

SELECT * FROM sys.dm_tran_locks WHERE resource_database_id = DB_ID() AND resource_associated_entity_id = OBJECT_ID(N'dbo.table1');

此外,在单独的SSMS窗口中,我可以在事务运行时完全查询表。

为什么MSSQL不尊重锁定提示?

(SQL Server 2016)

编辑1
有关这些锁如何工作的任何信息都很受欢迎,但是,现在的问题是SQL Server似乎没有强制执行我指定的锁。我的预感是这与行版本控制或相关的东西有关。

编辑2
我创建了这个Github gist。它需要.NET和外部库Dapper才能运行(可通过Nuget包获得)。

这是我注意到的有趣事情:

  • 即使先前已请求table1的查询,也可以针对UPDLOCK, HOLDLOCK运行SELECT语句。
  • 锁定时无法运行INSERT语句
  • 在锁定时,无法运行针对现有记录的UPDATE语句
  • 可以运行针对不存在的记录的UPDATE语句

以下是该Gist的控制台输出:

  

运行锁定SELECT开始 - 00:00:00.0165118
  运行非锁定SELECT开始 - 00:00:02.0155787
  运行非锁定SELECT完成 - 00:00:02.0222536
  运行INSERT开始 - 00:00:04.0156334
  运行UPDATE ALL开始 - 00:00:06.0259382
  运行UPDATE EXISTING开始 - 00:00:08.0216868
  运行更新不存在开始 - 00:00:10.0236223
  运行更新不存在完成 - 00:00:10.0268826
  运行锁定SELECT完成 - 00:00:31.3204120
  运行INSERT完成 - 00:00:31.3209670
  运行UPDATE ALL已完成 - 00:00:31.3213625
  运行UPDATE EXISTING完成 - 00:00:31.3219371

3 个答案:

答案 0 :(得分:3)

  

我正在尝试测试我想要锁定行的情况   正在阅读和更新

如果要锁定读取和更新行,则需要独占锁定,但UPDLOCK锁定提示请求更新锁定,而不是独占锁定。查询应为:

SELECT * FROM table1 WITH (XLOCK, HOLDLOCK, ROWLOCK)
WHERE Id = <some id>

此外,在READ COMMITTED SNAPSHOTSNAPSHOT隔离级别下,SELECT语句不会请求共享锁,只会请求架构稳定性锁。因此,SELECT语句可以读取行,尽管存在独占锁。令人惊讶的是,在READ COMMITTED隔离级别下,SELECT语句可能不会请求行级共享锁。您需要在SELECT语句中添加查询提示,以防止它读取锁定的行:

SELECT * FROM dbo.Table1 WITH (REPEATABLEREAD)
WHERE id = <some id>

使用REPEATABLEREAD锁定提示时,SELECT语句将请求共享锁并在事务期间保留它们,因此它不会读取独占锁定的行。请注意,使用READCOMMITTEDLOCK是不够的,因为在某些情况下SQL Server可能不会请求共享锁,如this博文中所述。

请查看Lock Compatibility Table

在默认隔离级别READ COMMITTED下,如果没有锁定提示,SELECT语句会为其读取的每一行请求共享锁,并且在读取该行后立即释放这些锁。但是,如果使用WITH (HOLDLOCK),则会保留共享锁,直到事务结束。考虑到锁兼容性表,在SELECT下运行的READ COMMITTED语句可以读取任何未被锁定的行(IX,SIX,X锁)。 INSERTUPDATEDELETE语句或带有SELECT提示的XLOCK语句请求独占锁定。

  

我希望在桌子上放置一个独占锁,并且   特别是对于行(如果我在主要上有一个WHERE语句   键)

     

我需要了解为什么SQL Server没有重新锁定锁定   赋予它的指令。 (即为什么不是专属锁定   表格,还是那行?)

UPDLOCK提示不请求独占锁,它请求更新锁。此外,可以在除行本身之外的其他资源上授予锁定,可以在表,数据页,索引页和索引键上授予锁。 SQL Server可以锁定的资源类型的完整列表是:DATABASE, FILE, OBJECT, PAGE, KEY, EXTENT, RID, APPLICATION, METADATA, HOBT, and ALLOCATION_UNIT。指定ROWLOCK提示时,SQL Server将锁定行,而不是页面,范围或表,SQL Server将锁定的实际资源是RIDKEY

答案 1 :(得分:0)

@Remus Rusuanu已经比我更好地解释了它here

从本质上讲 - 您总是可以阅读 UNLESS ,要求使用相同的锁定类型(或更严格的限制)。但是,如果您想要UPDATEDELETE,那么您将被屏蔽。但正如我所说,上面的链接解释得非常好。

答案 2 :(得分:0)

你的回答是正确的:

https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table

  

锁提示获取行级锁的ROWLOCK,UPDLOCK和XLOCK可能会锁定索引键而不是实际数据行。例如,如果表具有非聚簇索引,并且使用锁定提示的SELECT语句由覆盖索引处理,则会在覆盖索引中的索引键上获取锁定,而不是在基表中的数据行上获取锁定。 / p>

这就是为什么你得到索引锁(IX)而不是表行锁。

这解释了为什么你可以在运行第一个查询时阅读:

http://aboutsqlserver.com/2011/04/14/locking-in-microsoft-sql-server-part-1-lock-types/

  

更新锁(U)。这些锁是共享锁和独占锁之间的混合。 SQL Server在搜索需要修改的行时将它们与数据修改语句一起使用。例如,如果发出如下语句:“update MyTable set Column1 = 0其中Column1为null”SQL Server在搜索Column1时为其处理的每一行获取更新锁定为null。找到符合条件的行时,SQL Server会将(U)锁定转换为(X)。

你的UPDLock是一个更新锁。请注意,更新锁在搜索时是SHARED,在执行实际更新时更改为EXCLUSIVE。由于您的查询是带有更新锁定提示的选择,因此锁定是SHARED锁定。这将允许其他查询也读取行。