如何限制一个人一次可以运行的SQL存储过程?


12

我有一个存储过程,基本上从一个表中选择值,然后将它们插入到另一个表中,这是一种归档。我想避免多个人同时执行此操作。

在运行此过程时,我不希望其他任何人都可以启动该过程,但是我不希望序列化,而在完成后,其他人可以运行该过程。

我想要的是给其他尝试启动该程序的人,同时我正在运行该程序。

我已经尝试过使用sp_getapplock,但是我无法完全阻止该人运行该过程。

我还尝试使用sys.dm_exec_requests查找该过程并阻止该过程,尽管这确实可行,但我认为它不是最佳选择,因为在某些服务器上我没有运行sys.dm_exec_sql_text(sql_handle)的权限。

我这样做的最佳方法是什么?


3
您可以退后一步,并提供有关该过程正在执行的操作以及为什么要避免多个人同时运行的更多信息吗?可能有一种编码技术可以消除此要求,或者可以实施某种排队来处理事情。
AMtwo

Answers:


15

要添加到@ Tibor-Karaszi的答案中,设置锁定超时实际上并不会产生错误(我已经针对文档提交了PR)。sp_getapplock仅返回-1,因此您必须检查返回值。像这样:

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end


7

另一种选择是建立一个表来控制对该过程的访问。下面的示例显示了可能的表格以及可以使用该表格的过程。

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END

1
这与我在阅读问题后立即想到的想法非常相似(或可能与之相同),但是我对此有一个疑问,即我不确定如何解决并且无法在您的回答中看到它要么。我担心的是,如果在“做任何需要做的事情”部分发生什么事情该怎么办?IsLocked在这种情况下,如何将状态重置为0?我也很好奇您在COALESCE这里使用。@@ROWCOUNT像这样的语句后可以为null UPDATE吗?最后,只是个小问题,为什么THROW在这种情况下要在语句前加一个分号?
安德里(Andriy M)

锁定到期是处理该问题的一种方法。需要将其设置为合理的时间范围,在示例中,我将其设置为10分钟。您也可以将工作逻辑封装在try / catch块中,也可以在catch中解锁。我出于习惯使用COALESCE,但是@@ ROWCOUNT不能为NULL。领先的分号来自使用Visual Studio数据库项目,它抱怨是否不存在。两种方式都没有危害。
乔纳森·菲特

-1

我认为您正在尝试以错误的方式解决问题。您想要的是最大程度地保护数据库一致性。如果两个人同时运行一个存储过程,则可能会破坏数据库一致性。

为了防止各种数据库不一致,SQL标准具有四个事务隔离级别:

  • READ UNCOMMITTED,基本上是交易失去价值,其他交易则看到脏数据。不要使用这个!
  • 读已提交,其中事务仅看到已提交的数据,但是可能存在不一致之处,两个事务可以跨过对方的脚趾
  • 重复读解决了一种不一致的,不可重复的读
  • SERIALIZABLE,它保证存在一些虚拟顺序,在这些虚拟顺序中执行事务会导致其执行结果

但是,SQL标准针对这些数据库不一致情况采用了基于锁定的方法,并且出于性能方面的考虑,许多数据库采用了基于快照隔离的方法,该方法基本上具有以下级别:

  • READ COMMITTED,与基于锁定的数据库相同
  • 快照隔离,其中数据库看到所有数据的快照,并且如果它尝试更新已由其他事务更新的行,则将其取消,但是可能会发生一些众所周知的异常
  • SERIALIZABLE,与基于锁定的数据库中的SERIALIZABLE相同,但是这次以不同的方式实现,不是通过获取锁,而是通过确保没有序列化违规来实现,如果检测到此类违规,则取消事务

这些基于快照隔离的数据库中的事务取消可能听起来令人担忧,但是由于死锁,每个数据库都会再次取消事务,因此无论如何,任何合理的应用程序都需要能够重试事务。

您需要的是SERIALIZABLE隔离级别:它可以确保如果一个接一个地独立执行的事务导致良好状态,那么任何并行执行的事务也都将导致良好状态。幸运的是,迈克尔·卡希尔(Michael Cahill)在其博士论文中发现了快照隔离数据库如何轻松支持SERIALIZABLE隔离级别。

如果在快照隔离的数据库中使用SERIALIZABLE隔离级别,则如果两个人尝试同时运行存储过程,并且他们互相踩踏,则其中一个事务将被取消。

现在,SQL Server是否真正支持SERIALIZABLE隔离级别(而不是伪装在SERIALIZABLE关键字后面的快照隔离)?坦率地说,我不知道:我知道唯一支持它的数据库是PostgreSQL。

即使我未能给出具体的SQL Server建议,我仍然在发布此答案,因为PostgreSQL的用户以及可以考虑切换到PostgreSQL的其他数据库的用户都可以从我的答案中受益。另外,无法切换到PostgreSQL的非PostgreSQL数据库的用户可以向自己喜欢的数据库供应商施压,以提供真正的SERIALIZABLE隔离级别。


我认为“低票”是指有人调查了SQL Server是否具有SERIALIZABLE隔离级别,并发现它没有。
juhist

-2

我意识到“实际”问题可能更加复杂。

如果不是这样:如果使用插入和/或更新触发器进行归档,则可以避免您要解决的问题。

希望能对您
有所帮助-克里斯C.


1
“立即”是什么意思?紧接着什么?插入后?因此,一旦出现新行,立即将其发送到存档?还是更新后的意思?那么,任何数据更改都会触发归档吗?也许您应该更具体地考虑要建议的方案。
安德里(Andriy M)

归档可能太昂贵和/或太少而不值得在每个插入上进行,特别是如果源表频繁插入和/或源表与归档之间的事务安全需要昂贵的锁时。
underscore_d

@underscore_d是的,它可能太昂贵或不总是需要。这就是为什么我从那句话开始回答的原因the 'real' problem may be more complex。如果不是,触发器是一个很好的解决方案。另外,由于它是数据库的功能而不是自定义解决方案,因此它可能更易于测试和维护。
克里斯·康普顿

@AndriyM我立即删除了这个词,取而代之的是插入/更新触发器的引用。对困惑感到抱歉。
克里斯·康普顿

1
我重新阅读了问题,我认为我可以看到我困惑的根源。您在这里提出的建议更类似于审计而不是归档。据我了解,归档数据意味着将数据移动(例如,从一个表移动到另一个表)。但是,即使OP将其过程的功能概括为“一种归档”,他们也从未说过将从源中删除数据,只是从中选择数据并将其插入目标中。因此,我猜测您假设OP需要复制而不是移动其数据,在这种情况下,使用触发器可能是有意义的。
安德里·M
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.