我应该避免使用sp_getAppLock吗?

时间:2012-10-25 14:11:22

标签: sql-server sql-server-2008 tsql

我有一个存储过程,我想确保它不能同时执行。

我的(多线程)应用程序通过此存储过程对基础表执行所有必要的工作。

imo,锁定表本身是一个不必要的极端行动,所以当我发现sp_GetAppLock时,基本上强制执行一个关键部分,这听起来很理想。

我的计划是将存储过程包含在事务中,并设置具有事务范围的spGetAppLock。代码已成功编写和测试。

该代码现已提交审核,我被告知我不应该调用此函数。然而,当提出明显的问题“为什么不呢?”时,我得到的唯一理由是非常主观的,与任何形式的锁定都很复杂。

我不一定会买这个,但我想知道是否有人有任何目标的原因我应该避免这个构造。就像我说的那样,根据我的情况,一个关键部分对我来说听起来很理想。

TIA, 皮特

进一步信息:应用程序位于此之上,有2个线程T1和T2。每个线程正在等待不同的消息M1和M2。所涉及的业务逻辑表明只有M1和M2都到达后才能进行处理。存储过程记录Mx已到达(插入),然后检查My是否存在(选择)。内置锁定可以确保插入连续发生。但选择也需要连续发生,我认为我需要在内置功能之上做一些事情。

为了清楚起见,我希望“处理”恰好发生一次。所以我无法承受存储过程返回误报或漏报。我担心如果存储过程连续快速运行两次,那么两个“选择”可能会返回数据,表明它适合执行处理。

4 个答案:

答案 0 :(得分:7)

您不能依赖SQL Server内置并发控制机制的过程是什么?通常可以重写查询以允许真正的并发。

但是如果这个过程确实必须“单独”执行,那么在第一次访问时锁定表本身很可能比使用对sp_GetAppLock的调用快得多。听起来这个程序经常会被调用。如果是这种情况,您应该寻找一种方法,以最小的影响实现目标。


如果表中除了M1和M2之外不包含其他行,表锁定仍然是您最好的选择。

如果您有多个线程发送多个消息,您可以通过使用“serializable”作为事务级别来获得更细粒度,并在执行插入之前检查其他消息是否存在,但是在同一事务中。在这种情况下,为了防止死锁,请确保检查这两个消息,例如:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;  
BEGIN TRAN;
 SELECT 
 @hasM1 = MAX(CASE WHEN msg_type='M1' THEN 1 ELSE 0 END), 
 @hasM2 = MAX(CASE WHEN msg_type='M2' THEN 1 ELSE 0 END)
 FROM messages WITH(UPDLOCK)
 WHERE msg_type IN ('M1','M2')

 INSERT ...

 IF(??) EXEC do_other_stuff_and_delete_messages;
COMMIT

在COMMIT之前的IF语句中,您可以使用插入前收集的信息以及您插入的信息来决定是否需要进行额外的处理。

在该处理步骤中,请确保将这些邮件标记为已处理或将其删除,但仍在同一事务中。这将确保您不会两次处理这些消息。

SERIALIZABLE是唯一允许锁定尚不存在的行的事务隔离级别,因此带有“WITH(UPDLOCK)”的第一个select语句有效地防止在第一次执行仍在运行时插入另一行。 / p>

最后,有很多事情需要注意,可能会出错。您可能想要查看服务代理。你可以使用三个队列。一个用于M1型,一个用于M2型。每次消息到达这些队列时,都可以自动调用过程将令牌插入第三个队列。然后,第三个队列可以激活进程以检查两个消息是否存在并且是否有效。这将使整个过程异步,但为此可以很容易地限制队列3的响应,始终只进行一次检查。

msdn上的服务经纪人, 还要查看自动消息处理的“激活”。

答案 1 :(得分:6)

sp_GetAppLock就像许多其他工具一样,因此它可能被滥用,过度使用或正确使用。它与原始海报描述的问题类型完全匹配。 这是关于用法的一篇很好的MSSQL Tips帖子 防止多个用户同时运行相同的SQL Server存储过程 http://www.mssqltips.com/sqlservertip/3202/prevent-multiple-users-from-running-the-same-sql-server-stored-procedure-at-the-same-time/

答案 2 :(得分:4)

我们一直使用sp_getapplock,因为我们支持一些已经重新使用SQL后端的遗留应用程序,而SQL Server锁定模型并不完全匹配我们的应用逻辑。

我们倾向于采用'悲观'锁定模型,我们在允许用户编辑之前锁定实体,并在读取数据时广泛使用(NOLOCK)提示以绕过本机锁上的任何阻塞实际的表格。 sp_getapplock是一个很好的匹配。我们还使用它来强制执行大型多用户系统中的关键路径。你必须系统地了解你所说的锁。

我们发现通过这条路线没有大量用户/锁的性能问题,所以我认为没有理由不适合你。请注意,如果您的进程具有相同的命名锁,但不一定按相同的顺序,则可能会出现阻塞和死锁。

答案 3 :(得分:0)

您可以为每组消息创建一个带有标志的表,因此如果其中一个线程首先开始处理,则会将该标记标记为正在处理。

要确保在一个线程到达后正确阻止记录,请使用:

   SELECT ... FROM WITH(XLOCK,ROWLOCK,READCOMMITTED) ... WHERE ...

这段代码的和平将把独家锁定在记录意味着谁首先拥有它拥有该行。 然后你做你的更改并更新标志,其他线程将获得更新值,因为它将被Exclusive锁阻塞,直到第一个线程提交或回滚事务。

要使其工作,您始终需要使用XLOCK从表中选择记录,这样它将按预期工作。

希望这有帮助。

独家锁定证明:

    USE master
    GO

    IF OBJECT_ID('dbo.tblTest') IS NOT NULL
        DROP TABLE dbo.tblTest

    CREATE TABLE tblTest ( id int PRIMARY KEY )

    ;WITH cteNumbers AS (
        SELECT 1 N
        UNION ALL
        SELECT N + 1 FROM cteNumbers WHERE N<1000
    )
    INSERT INTO
        tblTest
    SELECT
        N 
    FROM
        cteNumbers
    OPTION (MAXRECURSION 0)

    BEGIN TRANSACTION

    SELECT * FROM dbo.tblTest WITH(XLOCK,ROWLOCK,READCOMMITTED) WHERE id = 1

    SELECT * FROM sys.dm_tran_locks WHERE resource_database_id = DB_ID('master')

    ROLLBACK TRANSACTION