为什么原子语句需要锁定提示?

时间:2018-03-17 21:10:12

标签: tsql transactions sql-server-2008-r2 locking hint

问题

将锁应用于以下语句有什么好处?

同样,如果我们不包含这些提示,我们会​​看到什么问题?即,他们是否可以防止竞争,改善表现,或者其他什么?也许他们被包括在内以防止一些我没有考虑的问题,而不是我所假设的竞争条件。

注意:这是一个问题的溢出: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 

要求

  • SQL用于从队列中检索未处理的记录。
  • 要获取的记录应该是状态为Ready(StatusId = 1)的队列中的第一条记录。
  • 可能有多个工作人员/会话处理来自此队列的消息。
  • 我们希望确保队列中的每个记录只被选取一次(即由一个工作者),并且每个工作者按照它们在队列中出现的顺序处理消息。
  • 一个工人比另一个工人工作得更快(例如,如果工人A拿起记录1,那么工人B拿起记录2,如果工人B在工人A完成处理记录1之前完成记录2的处理,则可以)。我们只关注获取记录的背景。
  • 没有正在进行的交易;即我们只是想从队列中取出记录;在我们将状态从Processing升级到Processed之前,我们无需将其锁定。

上下文的附加SQL:

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 

我的理解

我知道锁定需要这些提示的好处是:

  • UPDLOCK:因为我们选择记录来更新它的状态,所以我们需要确保在我们阅读之后读取此记录的任何其他会话但在我们更新之前将无法读取记录更新它的意图(或者更确切地说,这样的声明必须等到我们执行更新并在其他会话可以看到我们的记录及其新值之前释放锁定。)
  • ROWLOCK:虽然我们正在锁定记录,但我们希望确保我们的锁只影响我们锁定的行;即因为我们不需要锁定许多资源/我们不想影响其他进程/我们希望其他会话能够读取队列中的下一个可用项目,即使该项目与我们的锁定记录在同一页面中
  • READPAST:如果另一个会话已经从队列中读取一个项目,而不是等待该会话释放它的锁定,我们的会话应该选择队列中的下一个可用(未锁定)记录。

即。如果我们运行以下代码,我认为这是有道理的:

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`

然而,当选择和更新发生在同一个语句中时,我假设没有其他会话可以在我们的会话的读取和读取之间读取相同的记录。更新;所以不需要明确的锁定提示。

我是否从根本上误解了锁具的工作原理;或者这些提示是否与其他类似但不同的用例有关?

2 个答案:

答案 0 :(得分:1)

John是正确的,因为这些是优化,但在SQL世界中,这些优化可能意味着“快速”与“难以忍受的数据大小缓慢”之间的差异和/或“工作”与“不可用”之间的差异死锁乱七八糟。

readpast提示很清楚。对于其他两个,我觉得我需要添加更多的上下文:

  • ROWLOCK提示是为了防止页面锁定粒度扫描。锁定粒度(行与页面)在查询开始时预先确定,并基于查询将扫描的页数估计(第三个粒度,表,仅在特殊情况下使用,不适用于此处)。通常,出列操作不应该扫描这么多页面,以便引擎考虑页面粒度。但是,当引擎决定使用页面锁定粒度时,我已经看到“在野外”的情况,这会导致出列的阻塞和死锁
  • 需要UPDLOCK来防止升级锁定死锁情况。 UPDATE语句在逻辑上拆分为搜索需要更新的行,然后更新行。搜索需要锁定它评估的行。如果行符合条件(符合WHERE条件),则更新行,并且update始终是独占锁。所以问题是如何在搜索过程中锁定行?如果您使用共享锁,则两个UPDATE将查看同一行(它们可以,因为共享锁允许它们),两者都确定行符合条件并且都尝试将锁升级为独占 - >僵局。如果在搜索期间使用独占锁定,则不会发生死锁,但是UPDATE将在使用任何其他读取计算的所有行上发生冲突,即使该行不符合条件(更不用说不能在不破坏的情况下尽早释放独占锁定{ {3}})。这就是为什么存在U模式锁定的原因(与候选行的UPDATE评估不会阻止读取)但与另一个U不兼容(因此两个UPDATE不会死锁)。基于CTE的典型出队需要这一提示有两个原因:

    1. 因为CTE查询处理不能理解CTE里面的SELECT是UPDATE的目标,应该使用U模式锁和
    2. dequeue操作将始终在相同的行之后进行更新(行被“出列”),因此死锁频繁。

答案 1 :(得分:0)

TL;博士

它们用于高并发专用队列表方案中的性能优化。

冗长

我想通过related SO answer作者找到this quoted blog's我找到了答案。

这个建议似乎是针对一个非常具体的情况;用作队列的表是专用作为队列;即该表不用于任何其他目的。在这种情况下,锁定提示是有意义的。他们与防止竞争条件无关;它们通过避免(非常短期)阻塞来提高高并发性场景的性能。

  • ReadPast锁提高了高并发场景的性能;没有等待当前读取的记录被释放;锁定它的唯一因素是另一个“队列工作者”进程,所以我们可以安全地跳过知道该工作人员正在处理此记录。
  • RowLock确保我们一次不会锁定多行,因此请求邮件的下一个工作人员将获取下一条记录而不是跳过多条记录,因为它们位于锁定的记录中页。
  • UpdLock用于锁定;即RowLock表示要锁定的内容,但没有说必须有锁定,ReadPast确定遇到其他锁定记录时的行为,因此再次不会导致当前记录锁定。我怀疑这不是明确需要的,因为SQL无论如何都会在后台获取它(事实上,在链接的SO答案中只指定了ReadPast);但是为了完整性而被包含在块帖子中/显式地显示了SQL在后台隐式导致的锁定。

然而该帖子是为专用队列表编写的。如果该表用于其他事项(例如在the original question中,它是一个包含发票数据的表,恰好有一列用于跟踪已打印的内容),该建议可能不合适。即通过使用ReadPast锁定,您将跳过所有锁定的记录;并且无法保证这些记录被处理队列的其他工作人员锁定;他们可能因某些完全不相关的目的而被锁定。这将破坏FIFO要求。

鉴于此,我认为关联问题的my answer代表。即创建一个专用表来处理队列场景,或者在上下文或场景中考虑其他选项及其优缺点。