将锁应用于以下语句有什么好处?
同样,如果我们不包含这些提示,我们会看到什么问题?即,他们是否可以防止竞争,改善表现,或者其他什么?也许他们被包括在内以防止一些我没有考虑的问题,而不是我所假设的竞争条件。
注意:这是一个问题的溢出:SQL Threadsafe UPDATE TOP 1 for FIFO Queue
WITH nextRecordToProcess AS
(
SELECT TOP(1) Id, StatusId
FROM DemoQueue
WHERE StatusId = 1 --Ready for processing
ORDER BY DateSubmitted, Id
)
UPDATE nextRecordToProcess
SET StatusId = 2 --Processing
OUTPUT Inserted.Id
Processing
升级到Processed
之前,我们无需将其锁定。CREATE TABLE Statuses
(
Id SMALLINT NOT NULL PRIMARY KEY CLUSTERED
, Name NVARCHAR(32) NOT NULL UNIQUE
)
GO
INSERT Statuses (Id, Name)
VALUES (0,'Draft')
, (1,'Ready')
, (2,'Processing')
, (3,'Processed')
, (4,'Error')
GO
CREATE TABLE DemoQueue
(
Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED
, StatusId SMALLINT NOT NULL FOREIGN KEY REFERENCES Statuses(Id)
, DateSubmitted DATETIME --will be null for all records with status 'Draft'
)
GO
在讨论队列的各种博客中,以及导致此讨论的问题中,建议将上述声明更改为包含如下锁定提示:
WITH nextRecordToProcess AS
(
SELECT TOP(1) Id, StatusId
FROM DemoQueue WITH (UPDLOCK, ROWLOCK, READPAST)
WHERE StatusId = 1 --Ready for processing
ORDER BY DateSubmitted, Id
)
UPDATE nextRecordToProcess
SET StatusId = 2 --Processing
OUTPUT Inserted.Id
我知道锁定需要这些提示的好处是:
即。如果我们运行以下代码,我认为这是有道理的:
DECLARE @nextRecordToProcess BIGINT
BEGIN TRANSACTION
SELECT TOP (1) @nextRecordToProcess = Id
FROM DemoQueue WITH (UPDLOCK, ROWLOCK, READPAST)
WHERE StatusId = 1 --Ready for processing
ORDER BY DateSubmitted, Id
--and then in a separate statement
UPDATE DemoQueue
SET StatusId = 2 --Processing
WHERE Id = @nextRecordToProcess
COMMIT TRANSACTION
--@nextRecordToProcess is then returned either as an out parameter or by including a `select @nextRecordToProcess Id`
然而,当选择和更新发生在同一个语句中时,我假设没有其他会话可以在我们的会话的读取和读取之间读取相同的记录。更新;所以不需要明确的锁定提示。
我是否从根本上误解了锁具的工作原理;或者这些提示是否与其他类似但不同的用例有关?
答案 0 :(得分:1)
John是正确的,因为这些是优化,但在SQL世界中,这些优化可能意味着“快速”与“难以忍受的数据大小缓慢”之间的差异和/或“工作”与“不可用”之间的差异死锁乱七八糟。
readpast提示很清楚。对于其他两个,我觉得我需要添加更多的上下文:
需要UPDLOCK来防止升级锁定死锁情况。 UPDATE语句在逻辑上拆分为搜索需要更新的行,然后更新行。搜索需要锁定它评估的行。如果行符合条件(符合WHERE条件),则更新行,并且update始终是独占锁。所以问题是如何在搜索过程中锁定行?如果您使用共享锁,则两个UPDATE将查看同一行(它们可以,因为共享锁允许它们),两者都确定行符合条件并且都尝试将锁升级为独占 - >僵局。如果在搜索期间使用独占锁定,则不会发生死锁,但是UPDATE将在使用任何其他读取计算的所有行上发生冲突,即使该行不符合条件(更不用说不能在不破坏的情况下尽早释放独占锁定{ {3}})。这就是为什么存在U模式锁定的原因(与候选行的UPDATE评估不会阻止读取)但与另一个U不兼容(因此两个UPDATE不会死锁)。基于CTE的典型出队需要这一提示有两个原因:
答案 1 :(得分:0)
它们用于高并发专用队列表方案中的性能优化。
我想通过related SO answer作者找到this quoted blog's我找到了答案。
这个建议似乎是针对一个非常具体的情况;用作队列的表是专用作为队列;即该表不用于任何其他目的。在这种情况下,锁定提示是有意义的。他们与防止竞争条件无关;它们通过避免(非常短期)阻塞来提高高并发性场景的性能。
ReadPast
锁提高了高并发场景的性能;没有等待当前读取的记录被释放;锁定它的唯一因素是另一个“队列工作者”进程,所以我们可以安全地跳过知道该工作人员正在处理此记录。RowLock
确保我们一次不会锁定多行,因此请求邮件的下一个工作人员将获取下一条记录而不是跳过多条记录,因为它们位于锁定的记录中页。UpdLock
用于锁定;即RowLock
表示要锁定的内容,但没有说必须有锁定,ReadPast
确定遇到其他锁定记录时的行为,因此再次不会导致当前记录锁定。我怀疑这不是明确需要的,因为SQL无论如何都会在后台获取它(事实上,在链接的SO答案中只指定了ReadPast
);但是为了完整性而被包含在块帖子中/显式地显示了SQL在后台隐式导致的锁定。 然而该帖子是为专用队列表编写的。如果该表用于其他事项(例如在the original question中,它是一个包含发票数据的表,恰好有一列用于跟踪已打印的内容),该建议可能不合适。即通过使用ReadPast
锁定,您将跳过所有锁定的记录;并且无法保证这些记录被处理队列的其他工作人员锁定;他们可能因某些完全不相关的目的而被锁定。这将破坏FIFO要求。
鉴于此,我认为关联问题的my answer代表。即创建一个专用表来处理队列场景,或者在上下文或场景中考虑其他选项及其优缺点。