【问题标题】:TSQL mutual exclusive access in a stored procedure存储过程中的 TSQL 互斥访问
【发布时间】:2011-06-14 11:23:45
【问题描述】:

多个网络服务器访问 SQL Server 以获取数字代码,当此代码不存在时,它必须由 SQL Server 自动生成。

我需要确保即使有两个并发调用进来并且代码不存在,也只会创建一个代码并且两个调用都返回相同的代码。所以我必须这样做:

begin lock
  if code exists
    return code
  else
    generate code
    return code
end lock

我一直在阅读有关隔离级别和表锁定的一些内容,但我对所有这些都感到非常混乱。首先我认为 SERIALIZABLE 隔离级别是我需要的,但显然不是。

那么,您将如何在 TSQL 中完成“锁定”?

非常感谢。

更新:

当我尝试使用this 设置可序列化级别时遇到此错误:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE get_code 
AS
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    GO
    BEGIN TRANSACTION

    select code from codes where granted is null;
END
GO

消息 1018,级别 15,状态 1,程序 get_code,第 4 行附近的语法不正确 “可序列化”。如果这是为了 表提示的一部分,一个 WITH 关键字 现在需要括号。看 SQL Server 联机丛书 句法。消息 102,级别 15,状态 1, 第 5 行 'END' 附近的语法不正确。

什么意思?

【问题讨论】:

  • 如何知道代码是否已经存在?
  • 该死的!我还没有发现该关键字的用处:D
  • 详细了解 SQL Server 中的“GO”:OneTwo。如果您想让它起作用,请阅读我的回答...
  • 直到今天,我仍然不知道 GO 是干什么用的。承诺,是的。但不是GO。我认为这是 Microsoft 的另一个蹩脚功能。

标签: sql-server multithreading sql-server-2005 synchronization locking


【解决方案1】:

SERIALIZABLE 是锁定的隔离级别,而不是semaphore

在这种情况下它不会起作用,你要做的就是在 TXN 的末尾保持一个读锁,这不会阻止另一个进程读取代码。

您需要在事务模式下使用sp_getapplock。您可以将其配置为等待、立即轰炸等:由您决定

这是基于我来自Nested stored procedures containing TRY CATCH ROLLBACK pattern?的模板

ALTER PROCEDURE get_code 
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int, @result int;

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0 BEGIN TRANSACTION

    EXEC @result = sp_getapplock 'get_code', 'Exclusive', 'Transaction', 0 
    IF @result < 0
        RAISERROR('INFO: One at a time please`!', 16, 1);

    [...Perform work...]


    IF @starttrancount = 0 
        COMMIT TRANSACTION
    ELSE
        EXEC sp_releaseapplock 'get_code';
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION
    RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO

【讨论】:

  • 只是想知道,既然我们在这个话题上,你有没有发现我做的方式有任何错误(见我的回答)?
  • 太棒了,正是我想要的。只有一件事......你不应该在 CATCH 块中释放锁吗? (从 C# 开发人员的角度来看,如果引发错误,则不会调用该版本)
  • @vtortola:你可以,但它已经发布了,你必须测试/捕获它。当它是“事务”模式时,它只存在于事务中: SET XACT_ABORT ON 自动回滚事务。我多余的 ROLLBACK 也是如此。如果是嵌套事务,我会释放它
  • 我明白了,因此,如果您提交,您也不会释放锁定。有趣的。谢谢一百万。
【解决方案2】:

我就是这样做的。给定一个表MetaInfo,列MetaKey varchar(max) and MeatValueLong bigInt

注意,在我的例子中,我们的目标是在没有重复的情况下专门获得增加的价值。我使用行锁在这个单一操作上创建隔离。 (是的,我知道我可以使用插入和自动增量键,但还有一个附加要求,即调用者可以传入最小值。)

CREATE PROCEDURE [dbo].[uspGetNextID]
(
  @inID bigInt 
)
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRANSACTION

    -- This section can be removed if you want to pass in an id.
    SET @inID = 0

    UPDATE MetaInfo WITH (ROWLOCK) 
      SET MetaValueLong = CASE 
                            WHEN ISNULL(MetaValueLong,0) > @inID THEN MetaValueLong+1 
                            ELSE @inID+1
                          END 
    WHERE MetaKey = 'Internal-ID-Last'

    SELECT MetaValueLong 
    FROM MetaInfo
    WHERE MetaKey = 'Internal-ID-Last'

    COMMIT TRANSACTION 

END

【讨论】:

  • 应该提出一个新问题:它与 UPDATE 不同。而且它不是以队列为单位的表”,例如 stackoverflow.com/questions/939831/…
  • @gbn -- 您的评论暗示它是错误的 -- 我相信它按预期工作 -- 我应该提出一个问题,以便您可以告诉我它是如何工作不正常的吗?
  • 我认为值得与更广泛的受众单独讨论,因为老实说,我不是 100% 确定 :-)。它是被更新的同一行(您正在模拟一个 SEQUENCE),这与我的竞争条件链接不同,也与使用类似这个问题的信号量不同。对不起!
【解决方案3】:

是的,SET ISOLATION LEVEL SERIALIZABLE 正是您所需要的。它不允许脏写和脏读。可序列化事务中的所有 db-objets 都被锁定,因此其他连接只有在第一个提交或回滚时才能读取/写入。

【讨论】:

  • 我试过了但是报错,请看更新
  • 删除 SET TRANSACTION... 和 BEGIN TRAN 之间的 GO
  • -1 表示不理解 SERIALIZABLE:它不会阻止两个进程生成相同的代码。这不是信号量
  • 那是公平的 - 我没有仔细阅读这个问题。为您的回答 +1
猜你喜欢
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多