SQL存储过程中的互斥访问

时间:2011-06-14 11:23:46

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

多个Web服务器访问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
  

Msg 1018,Level 15,State 1,Procedure   get_code,第4行附近的语法不正确   'SERIALIZABLE'。如果是这样的话   表格提示的一部分,一个WITH关键字   现在需要括号和括号。看到   SQL Server联机丛书正确   句法。 Msg 102,Level 15,State 1,   第5行'END'附近的语法不正确。

这意味着什么?

3 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分: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

答案 2 :(得分:1)

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