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语句。 这是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 SNAPSHOT
和SNAPSHOT
隔离级别下, 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锁)。 独占锁由INSERT
, UPDATE
和DELETE
语句或具有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将锁定的实际资源是RID
和KEY
的
@Remus Rusuanu比我在这里所能做到的要好得多。
从本质上讲, 除非你要求相同的锁类型(或更具限制性),否则你总是可以阅读。 但是,如果你想UPDATE
或DELETE
那么你将被阻止。 但正如我所说,上面的链接解释得非常好。
您的回答在文档中是正确的:
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