我有一张桌子,我需要在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中完成它?
提前致谢!
答案 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
答案 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
。