没有“身份”的最佳方式获取下一个ID号码

时间:2009-04-15 11:29:48

标签: sql sql-server transactions locking deadlock

我必须在遗留数据库的表中插入一些记录,并且由于它被其他古代系统使用,因此更改表不是解决方案。

问题是目标表有一个int主键但没有标识规范。所以我必须找到下一个可用的ID并使用它:

select @id=ISNULL(max(recid)+1,1) from subscriber

但是,当我这样做时,我想阻止其他应用程序插入到表中,以便我们没有任何问题。我试过这个:

begin transaction
    declare @id as int
    select @id=ISNULL(max(recid)+1,1) from subscriber WITH (HOLDLOCK, TABLOCK)
    select @id
    WAITFOR DELAY '00:00:01'
    insert into subscriber (recid) values (@id)
commit transaction
select * from subscriber

在SQL Management Studio中的两个不同窗口中,一个事务总是作为死锁牺牲品被杀死。

我首先尝试了SET TRANSACTION ISOLATION LEVEL SERIALIZABLE同样的结果......

有什么好的建议,我怎么能确保我获得下一个id并使用它而不会冒着别人(或我!)的风险被冲洗?

很抱歉没有提到这个,但这是一个SQL 2000服务器所以我不能使用像FOR UPDATE和OUTPUT这样的东西

更新:这是适合我的解决方案:

BEGIN TRANSACTION
    DECLARE @id int

    SELECT  @id=recid
    FROM    identities WITH (UPDLOCK, ROWLOCK)
    WHERE table_name = 'subscriber'

    waitfor delay '00:00:06'

    INSERT INTO subscriber (recid) values (@id)

    UPDATE identities SET recid=recid+1 
    WHERE table_name = 'subscriber'

COMMIT transaction

select * from subscriber

WAITFOR是这样的,我可以拥有多个连接并多次启动查询以激发并发性。

感谢Quassnoi的回答以及所有其他贡献者!真棒!

4 个答案:

答案 0 :(得分:10)

创建另一个表:

t_identity (id INT NOT NULL PRIMARY KEY CHECK (id = 1), value INT NOT NULL)

只有一行,锁定此行,并在每次需要value时将IDENTITY增加一。

要在单个语句中锁定,递增和返回新值,请使用:

UPDATE  t_identity
SET     value = value + 1
OUTPUT  INSERTED.value

如果您不想更新,只需锁定,然后发出:

SELECT  value
FROM    t_identity WITH (UPDLOCK, ROWLOCK)

这将锁定表直到事务结束。

如果您在弄乱t_identity之前总是首先锁定ancient_table,那么您将永远不会陷入僵局。

答案 1 :(得分:4)

添加另一个带有标识列的表,并使用此新表和列为旧表选择/生成您的标识值。

更新:根据INSERTS的频率(以及现有行数 e ),您可以在<\ n>处种植新的IDENTITY值strong> e + x ,其中 x 足够大。这样可以避免与传统插入冲突。一个悲伤的解决方案,肯定是一个不完美的解决方案,但需要考虑一下?

答案 2 :(得分:3)

编辑这基本上是@Quassnoi的目的,我只是在一个循环中实现它,这样你就可以同时对多个窗口运行它,看它效果很好。

设置:

创建用户的现有表格:

create table Subscriber
(
recid  int not null primary key
)

创建新表来跟踪丢失的身份,如果多个表需要这样做,你可以添加一个额外的列来跟踪表,但我在这个例子中没有这样做:

CREATE TABLE SubscriberIDs
(
SubscriberID int
)
insert into SubscriberIDs values (0) --row must exist first

创建测试脚本,将其放入多个窗口并同时运行:

declare @idtable table --will hold next ID to use
(
id int
)
declare @x  int
declare @y  int
set @x=0
while @x<5000 --set up loop
begin
    set @x=@x+1
    begin transaction
    --get the next ID to use, lock out other users
    UPDATE SubscriberIDs
        SET SubscriberID= SubscriberID+ 1
        OUTPUT  INSERTED.SubscriberID
        INTO @idtable
    --capture the next id from temp table variable
    select @y=id from @idtable
    --print @y
    --use the next id in the actual table
    insert into subscriber values (@y)

    commit
    --print @x
    waitfor delay '00:00:00.005'
end --while

<强> -------------------------------------------- -------------------
编辑这是我原来的尝试,最终会在循环和多个窗口同时运行时遇到一些死锁。上述方法始终有效。我尝试了所有事务组合,使用(holdlock),并设置事务隔离级别可序列化等,但无法使其运行以及上述方法。

设置:

create table subscriber
(
recid  int not null primary key
)

用于捕获id:

declare @idtable table
(
id int
)

插入:

insert into subscriber
    OUTPUT INSERTED.recid
        recid
    INTO @idtable
    SELECT ISNULL(MAX(recid),0)+1 FROM subscriber

列出新ID:

select * from @idtable

列出所有ID:

select * from subscriber

答案 3 :(得分:0)

你不应该在这里遇到僵局,因为第二个应该等待第一个完成。您的问题是您正在创建一个事务,然后在该事务中添加另一个锁。

此外,您将获得ID,然后在两个单独的语句中使用它,而您可以在一个解决方案中完成所有操作:

set transaction isolation level serializable
begin transaction
    insert into subscriber (recid) 
       SELECT (select ISNULL(max(recid)+1,1) from subscriber)
commit transaction
select * from subscriber

这应该确保您的插入物只有一致性。但是,当您指定遗留应用程序也在使用此表时,您是否可以确定在插入新记录时它不会与此冲突?