26

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

我的(多线程)应用程序通过这个存储过程在基础表上完成所有必要的工作。

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

我的计划是将存储过程封装在事务中并设置spGetAppLock事务范围。该代码已成功编写和测试。

代码现在已经提交审查,我被告知我不应该调用这个函数。然而,当问一个明显的问题“为什么不?”时,我得到的唯一原因是高度主观的,与任何形式的锁定都复杂有关。

我不一定会买这个,但我想知道是否有人有任何客观原因为什么我应该避免这种结构。就像我说的那样,考虑到我的情况,关键部分对我来说是一种理想的方法。

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

为清楚起见,我希望“处理”只发生一次。所以我不能让存储过程返回误报或漏报。我担心如果存储的过程很快连续运行两次,那么两个“选择”都可能返回表明它适合执行处理的数据。

4

4 回答 4

9

哪些程序不能依赖 SQL Server 内置的并发控制机制?通常可以重写查询以实现真正的并发性。

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


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

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

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 是唯一允许锁定尚不存在的行的事务隔离级别,因此第一个带有 的 select 语句WITH(UPDLOCK)有效地防止在第一次执行仍在运行时插入另一行。

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

msdn上的服务代理,还可以查看自动消息处理的“激活”。

于 2012-10-25T14:20:35.683 回答
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/

于 2015-06-02T15:54:38.293 回答
5

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

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

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

于 2015-06-08T10:18:48.323 回答
0

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

为了确保记录在其中一个线程到达时正确阻塞,请使用:

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

这种代码和平将在记录上设置排他锁,这意味着首先到达它的人拥有该行。然后你做你的更改和更新标志,其他线程将获得更新的值,因为它将被排他锁阻塞,直到第一个线程提交或回滚事务。

为此,您始终需要使用 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
于 2012-10-25T15:58:10.380 回答