Dea*_*ean 11 sql-server sql-server-2008-r2 locking
我不确定是否有一个命名模式,或者是否没有,因为这是一个糟糕的主意。但我需要我的服务在主动/主动负载平衡环境中运行。这只是应用程序服务器。数据库将位于单独的服务器上。我有一个服务,需要为表中的每条记录运行一个过程。此过程可能需要一两分钟,并且每 n 分钟重复一次(可配置,通常为 15 分钟)。
有一个需要此处理的 1000 条记录表,以及针对同一数据集运行的两个服务,我希望每个服务“签出”要处理的记录。我需要确保一次只有一个服务/线程在处理每条记录。
我有同事过去使用过“锁表”。将一条记录写入此表以在逻辑上锁定另一个表中的记录(顺便说一句,该其他表非常静态,并且添加了非常偶然的新记录),然后删除以释放锁定。
我想知道如果新表有一个列指示它何时被锁定,并且它当前被锁定,而不是不断插入删除,是否会更好。
有没有人有这种事情的提示?是否存在长期(ish)期限逻辑锁定的既定模式?关于如何确保一次只有一项服务获取锁的任何提示?(我的同事使用 TABLOCKX 锁定整个表。)
Sol*_*zky 13
我不喜欢额外的“锁定”表,也不喜欢锁定整个表以获取下一条记录的想法。我明白为什么要这样做,但这也会损害正在更新以释放锁定记录的操作的并发性(当两个进程不可能在同一记录上锁定相同的记录时,两个进程肯定不能为此而争吵)同时)。
我的偏好是将 ProcessStatusID(通常为 TINYINT)列添加到包含正在处理的数据的表中。是否有 LastModifiedDate 字段?如果没有,那么应该添加它。如果是,那么这些记录是否会在此处理之外更新?如果可以在此特定过程之外更新记录,则应添加另一个字段来跟踪 StatusModifiedDate(或类似的内容)。对于这个答案的其余部分,我将只使用“StatusModifiedDate”,因为它的含义很明确(实际上,即使当前没有“LastModifiedDate”字段,也可以用作字段名称)。
ProcessStatusID 的值(应该放入一个名为“ProcessStatus”的新查找表中,外键指向该表)可以是:
在这一点上,可以安全地假设从应用程序中,它只是想获取下一个要处理的记录,而不会传入任何内容来帮助做出该决定。因此,我们想要获取设置为“已完成”/“待定”的最旧(至少在 StatusModifiedDate 方面)记录。类似的东西:
SELECT TOP 1 pt.RecordID
FROM ProcessTable pt
WHERE pt.StatusID = 1
ORDER BY pt.StatusModifiedDate ASC;
Run Code Online (Sandbox Code Playgroud)
我们还希望同时将该记录更新为“In Process”,以防止其他进程获取它。我们可以使用OUTPUT子句让我们在同一个事务中执行 UPDATE 和 SELECT:
UPDATE TOP (1) pt
SET pt.StatusID = 2,
pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID
FROM ProcessTable pt
WHERE pt.StatusID = 1;
Run Code Online (Sandbox Code Playgroud)
这里的主要问题是,虽然我们可以TOP (1)在UPDATE操作中执行 a ,但没有办法执行ORDER BY. 但是,我们可以将其包装在 CTE 中以结合这两个概念:
;WITH cte AS
(
SELECT TOP 1 pt.RecordID
FROM ProcessTable pt (READPAST, ROWLOCK, UPDLOCK)
WHERE pt.StatusID = 1
ORDER BY pt.StatusModifiedDate ASC;
)
UPDATE cte
SET cte.StatusID = 2,
cte.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID;
Run Code Online (Sandbox Code Playgroud)
显而易见的问题是,两个同时执行 SELECT 的进程是否可以抓取相同的记录。我很确定带有 OUTPUT 子句的 UPDATE,特别是结合 READPAST 和 UPDLOCK 提示(更多细节见下文),会很好。但是,我还没有测试过这个确切的场景。如果由于某种原因上述查询没有处理竞争条件,则添加以下内容:应用程序锁。
上面的 CTE 查询可以包含在sp_getapplock和sp_releaseapplock 中,为进程创建一个“守门人”。这样做时,一次只能进入一个进程以运行上述查询。其他进程将被阻塞,直到具有应用锁的进程释放它。由于整个过程的这一步只是获取 RecordID,所以它相当快并且不会长时间阻塞其他进程。而且,就像 CTE 查询一样,我们不会阻塞整个表,从而允许对其他行进行其他更新(将它们的状态设置为“已完成”或“错误”)。本质上:
BEGIN TRANSACTION;
EXEC sp_getapplock @Resource = 'GetNextRecordToProcess', @LockMode = 'Exclusive';
{CTE UPDATE query shown above}
EXEC sp_releaseapplock @Resource = 'GetNextRecordToProcess';
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)
应用程序锁非常好,但应谨慎使用。
最后,您只需要一个存储过程来处理将状态设置为“已完成”或“错误”。这可以是一个简单的:
CREATE PROCEDURE ProcessTable_SetProcessStatusID
(
@RecordID INT,
@ProcessStatusID TINYINT
)
AS
SET NOCOUNT ON;
UPDATE pt
SET pt.ProcessStatusID = @ProcessStatusID,
pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
FROM ProcessTable pt
WHERE pt.RecordID = @RecordID;
Run Code Online (Sandbox Code Playgroud)
表提示(可在提示 (Transact-SQL) - 表中找到):
READPAST(似乎适合这个确切的场景)
指定数据库引擎不读取被其他事务锁定的行。指定 READPAST 时,将跳过行级锁。也就是说,数据库引擎跳过行而不是阻塞当前事务,直到释放锁...READPAST 主要用于在实现使用 SQL Server 表的工作队列时减少锁定争用。使用 READPAST 的队列读取器跳过被其他事务锁定的队列条目到下一个可用的队列条目,而不必等到其他事务释放它们的锁。
ROWLOCK(为了安全)
指定在通常采用页锁或表锁时采用行锁。
上锁
指定在事务完成之前获取并保持更新锁。UPDLOCK 仅在行级或页级对读取操作采用更新锁。
| 归档时间: |
|
| 查看次数: |
4179 次 |
| 最近记录: |