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

con*_*att 7 database sql-server 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
Run Code Online (Sandbox Code Playgroud)

我正在学习SQL锁是如何工作的,我正在尝试测试一种情况,我想锁定一行不被读取和更新.从这篇文章开始,这个任务的一些灵感,这是我试图解决的原始问题.

当我运行这个T-SQL时:

BEGIN TRANSACTION

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

COMMIT TRANSACTION
Run Code Online (Sandbox Code Playgroud)

我希望在表上放置一个独占锁,特别是对于行(如果我在主键上有一个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');
Run Code Online (Sandbox Code Playgroud)

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

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

(SQL Server 2016)

编辑1
有关这些锁如何工作的任何信息,但是,当前的问题是SQL Server似乎没有强制执行我指定的锁.我的预感是,这与行版本控制或相关的东西有关.

编辑2
我创建了这个Github要点.它需要.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
运行更新全部开始 - 00: 00:06.0259382
运行更新现有开始 - 00:00:08.0216868
运行更新不存在开始 - 00:00:10.0236223
运行更新不存在完成 - 00:00:10.0268826
运行锁定SELECT完成 - 00:00:31.3204120
运行INSERT完成- 00:00:31.3209670
运行更新全部完成 - 00:00:31.3213625
运行更新现有完成 - 00:00:31.3219371

Jes*_*pez 5

我正在尝试测试一种情况,我想锁定一行不被读取和更新

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

SELECT * FROM table1 WITH (XLOCK, HOLDLOCK, ROWLOCK)
WHERE Id = <some id>
Run Code Online (Sandbox Code Playgroud)

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

SELECT * FROM dbo.Table1 WITH (REPEATABLEREAD)
WHERE id = <some id>
Run Code Online (Sandbox Code Playgroud)

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

请查看锁兼容性表

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

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

我需要了解为什么 SQL Server 不尊重给予它的锁定指令。(即为什么表或行上没有独占锁?)

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


小智 1

@Remus Rusuanu 的解释比我在这里解释得更好。

本质上 - 你总是可以阅读,除非你要求相同的锁类型(或更严格)。但是,如果您愿意,UPDATE否则DELETE您将被阻止。但正如我所说,上面的链接解释得很好。