如何在存储过程中设置锁?

Fro*_*840 1 sql-server multithreading stored-procedures

我在 SQL 服务器数据库上有一个长时间运行的存储过程。我不希望它每十分钟运行一次以上。

存储过程运行后,我想将最新结果存储在一个LatestResult表中,针对某个时间,并让对该过程的所有调用在接下来的十分钟内返回该结果。

这相对简单,但我们发现,由于该过程检查LatestResult表并更新它,当两个用户同时调用该过程时,大型用户群会遇到许多死锁。

在客户端/线程情况下,我会通过使用锁来解决这个问题,让第一个用户锁定函数,第二个用户遇到锁,等待结果,第一个用户完成他们的过程调用,更新LatestResult表,并解锁第二个用户,然后他从LatestResult表中获取结果。

有没有办法在 SQL Server 中完成这种锁定?

编辑:

这基本上是没有错误检查调用的代码的外观:

DECLARE @LastChecked AS DATETIME
DECLARE @LastResult AS NUMERIC(18,2)
SELECT TOP 1 @LastChecked = LastRunTime, @LastResult = LastResult FROM LastResult

DECLARE @ReturnValue AS NUMERIC(18,2)

IF DATEDIFF(n, @LastChecked, GetDate()) >= 10 OR NOT @LastResult = 0
BEGIN 
    SELECT @ReturnValue = ABS(ISNULL(SUM(ISNULL(Amount,0)),0)) FROM Transactions WHERE ISNULL(DeletedFlag,0) = 0 GROUP BY GroupID ORDER BY ABS(ISNULL(SUM(ISNULL(Amount,0)),0))
        UPDATE LastResult SET LastRunTime = GETDATE(), LastResult = @ReturnValue
        SELECT @ReturnValue
    END
ELSE
BEGIN
    SELECT @LastResult
END
Run Code Online (Sandbox Code Playgroud)

我不太确定分组发生了什么,但我发现了一个测试系统,其中执行时间在 4 秒左右。

我认为有一些工作计划归档其中一些记录并将它们归结为运行总数,这可能会有所帮助,因为那四秒表中有几百万行......

Sol*_*zky 5

这是使用应用程序锁(请参阅sp_getapplocksp_releaseapplock)的有效机会,因为它是针对您定义的概念而不是针对任何给定表中的任何特定行的锁。这个想法是,你创建一个事务,然后创建这个具有不确定性的任意锁,其他进程将等待输入那段代码,直到锁被释放。这就像lock()在应用程序层一样工作。该@Resource参数是任意的“概念”的标签。在更复杂的情况下,您甚至可以连接 CustomerID 或其中的某些内容以进行更细粒度的锁定控制。

DECLARE @LastChecked DATETIME,
        @LastResult NUMERIC(18,2);
DECLARE @ReturnValue NUMERIC(18,2);

BEGIN TRANSACTION;
EXEC sp_getapplock @Resource = 'check_timing', @LockMode = 'Exclusive';

SELECT TOP 1 -- not sure if this helps the optimizer on a 1 row table, but seems ok
       @LastChecked = LastRunTime,
       @LastResult = LastResult
FROM LastResult;

IF (DATEDIFF(MINUTE, @LastChecked, GETDATE()) >= 10 OR @LastResult <> 0)
BEGIN 
   SELECT @ReturnValue = ABS(ISNULL(SUM(ISNULL(Amount, 0)), 0))
   FROM   Transactions
   WHERE  DeletedFlag = 0
   OR     DeletedFlag IS NULL;

   UPDATE LastResult
   SET    LastRunTime = GETDATE(),
          LastResult = @ReturnValue;
END;
ELSE
BEGIN
   SET @ReturnValue = @LastResult; -- This is always 0 here
END;

SELECT @ReturnValue AS [ReturnValue];

EXEC sp_releaseapplock @Resource = 'check_timing';
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

您需要自己管理错误/回滚(如链接的 MSDN 文档中所述),因此放入通常的 TRY/CATCH。但是,这确实可以让您管理这种情况。

如果对这个过程的争用有任何顾虑,那么在锁定资源后立即执行的查找是从单行表中进行的 SELECT,然后是一个(理想情况下)只返回最后一个已知值的 IF 语句如果 10 分钟计时器尚未结束。因此,大多数呼叫应该处理得相当快。

请注意: sp_getapplock /sp_releaseapplock应谨慎使用;应用程序锁绝对可以非常方便(例如在这种情况下),但只有在绝对必要时才应该使用它们。