使用TABLOCKX进行SQLserver多线程锁定

时间:2011-11-15 09:47:03

标签: sql sql-server multithreading tsql locking

我有一个带有2个字段的表“tbluser”:

  • userid = integer(autoincrement)
  • user = nvarchar(100)

我有一个使用此表的多线程/多服务器应用程序。

我想完成以下任务:

  • 保证字段用户在我的表格中是唯一的
  • 保证组合用户ID /用户在每个服务器的内存中是唯一的

我有以下存储过程:

CREATE PROCEDURE uniqueuser @user nvarchar(100) AS
BEGIN
BEGIN TRAN
    DECLARE @userID int
    SET  nocount ON
    SET  @userID = (SELECT @userID FROM tbluser WITH (TABLOCKX) WHERE  [user] = @user)
    IF @userID <> ''
    BEGIN
        SELECT  userID = @userID
    END
    ELSE
    BEGIN
        INSERT INTO  tbluser([user]) VALUES (@user)
        SELECT userID = SCOPE_IDENTITY() 
    END 
COMMIT TRAN
END

应用程序基本上调用存储过程并提供用户名作为参数。 存储过程要么获取用户ID,要么插入用户(如果是新用户)。

我是否正确假设该表已被锁定(只有一个服务器可以插入/查询)?

2 个答案:

答案 0 :(得分:4)

我确信以下建议可能会对将来有所帮助。

而不是(TABLOCKX),请尝试(TABLOCKX, HOLDLOCK) 甚至更好,如果这是写入tbluser的唯一过程,你可以完全使用TABLOCKX减少一大堆代码而不通过

CREATE PROCEDURE uniqueuser @user nvarchar(100)
AS
--BEGIN TRAN NOT NECESSARY!
    INSERT tbluser WITH (TABLOCKX) ([user])
        SELECT @user
        WHERE NOT EXISTS(SELECT 1 FROM tbluser WHERE [user]=@user)
--COMMIT TRAN NOT NECESSARY!
SELECT userID FROM tbluser WHERE [user]=@user

这样,insert语句(在INSERT期间自动创建事务)是您需要表锁定的唯一时间。 (是的,我在带有和不带TABLOCKX的两个窗口上对此进行了压力测试,看看它在发布我的消息之前是如何流行的。)

答案 1 :(得分:2)

如果您想保证用户是唯一的,方式是唯一约束

ALTER  TABLE tbluser WITH CHECK
   ADD CONSTRAINT UQ_tbluser_user UQNIUE (user);

不要“推出自己的”独特检查:它会失败。

服务器内存中的缓存数据符合相同的约束

我会这样做。首先查找用户,如果未找到插入,则以防万一。我会使用OUTPUT参数

CREATE PROCEDURE uniqueuser
  @user nvarchar(100)
  -- ,@userid int = NULL OUTPUT
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE @userID int;

BEGIN TRY
    SELECT @userID FROM tbluser WHERE [user] = @user;
    IF @userID IS NULL
    BEGIN
        INSERT INTO  tbluser([user]) VALUES (@user);
        SELECT userID = SCOPE_IDENTITY() ;
    END
END TRY
BEGIN CATCH
    -- test for a concurrent call that just inserted before we did
    IF ERROR_NUMBER() = 2627
        SELECT @userID FROM tbluser WHERE [user] = @user;
    ELSE
       -- do some error handling
END CATCH
-- I prefer output parameter for this SELECT @userID AS UserID
GO

编辑:为什么TABLOCKX失败......

  • 您只能在SELECT期间锁定表格。
  • 同时运行的第二个进程将在进程1释放锁定后开始读取表
  • 两个进程都可以有@userID IS NULL,因为进程1尚未执行INSERT
  • 进程2在INSERTS
  • 时出错

这是因为TABLOCKX修改了锁定隔离和粒度,而不是持续时间。

编辑2:用于SQL Server 2000

CREATE PROCEDURE uniqueuser
   @user nvarchar(100)
   -- ,@userid int = NULL OUTPUT
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE @userID int;

SELECT @userID FROM tbluser WHERE [user] = @user;
IF @userID IS NULL
BEGIN
    INSERT INTO  tbluser([user]) VALUES (@user);
    IF @@ERROR = 2627
        SELECT @userID FROM tbluser WHERE [user] = @user;
    ELSE
        RAISERROR ('the fan needs cleaning', 16, 1);   
    SELECT userID = SCOPE_IDENTITY();
END
GO