存储过程中的SQLServer锁表

时间:2010-11-10 10:40:05

标签: sql sql-server locking

我有一张桌子,我需要在99%的时间内自动分配ID(其他1%的规则使用标识列排除)。所以我有一个存储过程来获取下一行的ID:

select @nextid = lastid+1 from last_auto_id
check next available id in the table...
update last_auto_id set lastid = @nextid

检查必须检查用户是否已手动使用ID并找到下一个未使用的ID。

当我连续调用它时,它正常工作,返回1,2,3 ...我需要做的是提供一些锁定,其中多个进程同时调用它。理想情况下,我只是需要它来独占锁定此代码周围的last_auto_id表,以便第二个调用必须等待第一个更新表才能运行它的select。

在Postgres中,我可以做一些像'LOCK TABLE last_auto_id;'的事情。显式锁定表。有任何想法如何在SQL Server中完成它?

提前致谢!

5 个答案:

答案 0 :(得分:5)

更新后,将lastid增加1,并在单个事务中将此值分配给本地变量。

修改

感谢Dave和Mitch指出原始解决方案的隔离级别问题。

UPDATE  last_auto_id WITH (READCOMMITTEDLOCK)
SET     @nextid = lastid = lastid + 1

答案 1 :(得分:3)

你们之间有人回答了我的问题。我正在进行自己的回复,整理出一篇文章中的工作解决方案。关键似乎是事务方法,在last_auto_id表上有锁定提示。将事务隔离设置为可序列化似乎会产生死锁问题。

这就是我所拥有的(编辑后显示完整的代码,希望我能得到更多答案......):

DECLARE @Pointer AS INT

BEGIN TRANSACTION

-- Check what the next ID to use should be
SELECT @NextId = LastId + 1 FROM Last_Auto_Id WITH (TABLOCKX) WHERE Name = 'CustomerNo'

-- Now check if this next ID already exists in the database
IF EXISTS (SELECT CustomerNo FROM Customer
           WHERE ISNUMERIC(CustomerNo) = 1 AND CustomerNo = @NextId)
BEGIN
  -- The next ID already exists - we need to find the next lowest free ID
  CREATE TABLE #idtbl ( IdNo int )

  -- Into temp table, grab all numeric IDs higher than the current next ID
  INSERT INTO #idtbl
  SELECT CAST(CustomerNo AS INT) FROM Customer
  WHERE ISNUMERIC(CustomerNo) = 1 AND CustomerNo >= @NextId
  ORDER BY CAST(CustomerNo AS INT)

  -- Join the table with itself, based on the right hand side of the join
  -- being equal to the ID on the left hand side + 1.  We're looking for
  -- the lowest record where the right hand side is NULL (i.e. the ID is
  -- unused)
  SELECT @Pointer = MIN( t1.IdNo ) + 1 FROM #idtbl t1
  LEFT OUTER JOIN #idtbl t2 ON t1.IdNo + 1 = t2.IdNo
  WHERE t2.IdNo IS NULL
END

UPDATE Last_Auto_Id SET LastId = @NextId WHERE Name = 'CustomerNo'

COMMIT TRANSACTION

SELECT @NextId

这会在事务开始时取出一个独占表锁,然后成功排队任何进一步的请求,直到此请求更新表并提交它的事务为止。

我已经编写了一些C代码来处理来自六个会话的并发请求并且它运行良好。

但是,我确实有一个担心,即锁定'提示'一词 - 有人知道SQLServer是将其视为明确的指令还是只是提示(即可能不会总是遵守它?)

答案 2 :(得分:2)

这个解决方案怎么样? 没有桌面锁定是完美的!!!

DECLARE  @NextId INT

UPDATE   Last_Auto_Id 
SET      @NextId = LastId = LastId + 1
WHERE    Name = 'CustomerNo'

SELECT   @NextId 

Update statement always uses a lock to protect its update

答案 3 :(得分:1)

你可能想考虑死锁。这通常发生在多个用户同时使用存储过程时。为了避免死锁并确保用户的每个查询都会成功,您需要在更新失败期间进行一些处理,为此,您需要尝试catch。这仅适用于Sql Server 2005,2008。

DECLARE @Tries tinyint

SET @Tries = 1

WHILE @Tries <= 3

BEGIN

  BEGIN TRANSACTION

  BEGIN TRY

-- this line updates the last_auto_id

update last_auto_id set lastid = lastid+1

   COMMIT

   BREAK
  END TRY

  BEGIN CATCH

   SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() as ErrorMessage

   ROLLBACK

   SET @Tries = @Tries + 1

   CONTINUE

 END CATCH

END

答案 4 :(得分:1)

我更喜欢在第二个表中使用identity字段来执行此操作。如果您创建lastid identity,那么您只需在该表中插入一行并选择@scope_identity以获取新值,您仍然具有{{1}的并发安全性即使主表中的id字段不是identity