UPDLOCK和HOLDLOCK查询不会创建预期的锁定

我有下面的表格:

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锁是如何工作的,我试图测试一种情况,我想锁定一行以免被读取和更新。 这篇文章中的一些启示来源于这篇文章,这里是我试图解决的原始问题。

当我运行这个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的要点。 它需要.NET和外部库Dapper运行(通过Nuget包提供)。

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

  • 即使已请求使用UPDLOCK, HOLDLOCK的先前查询,也可以针对table1运行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
    运行UPDATE NON-EXISTING开始 - 00:00:10.0236223
    运行UPDATE NON-EXISTING完成 - 00:00:10.0268826
    运行锁定SELECT Finished - 00:00:31.3204120
    运行INSERT Finished - 00:00:31.3209670
    运行UPDATE ALL完成 - 00:00:31.3213625
    运行UPDATE EXISHTING完成 - 00:00:31.3219371


    我试图测试一种情况,我想锁定一行以免被读取和更新

    如果你想锁定一行来读取和更新你需要一个排它锁,但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可能不会请求共享锁。

    请查看锁兼容性表

    在缺省隔离级别READ COMMITTED ,并且在没有锁定提示的情况下, SELECT语句为它读取的每一行请求共享锁,并且在读取该行后立即释放这些锁。 但是,如果使用WITH (HOLDLOCK) ,共享锁将保留到事务结束。 考虑到锁兼容性表,在READ COMMITTED下运行的SELECT语句可以读取任何未锁定的行(IX,SIX,X锁)。 独占锁由INSERTUPDATEDELETE语句或具有XLOCK提示的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


    @Remus Rusuanu比我在这里所能做到的要好得多。

    从本质上讲, 除非你要求相同的锁类型(或更具限制性),否则你总是可以阅读。 但是,如果你想UPDATEDELETE那么你将被阻止。 但正如我所说,上面的链接解释得非常好。


    您的回答在文档中是正确的:

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

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

    这就是为什么你得到一个索引锁(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锁。 这将允许其他查询也读取行。

    链接地址: http://www.djcxy.com/p/859.html

    上一篇: UPDLOCK and HOLDLOCK query not creating the expected lock

    下一篇: How to generate a rolling std line chart in dc.js/reductio/crossfilter