我必须在遗留数据库的表中插入一些记录,并且由于它被其他古代系统使用,因此更改表不是解决方案。
问题是目标表有一个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的回答以及所有其他贡献者!真棒!
答案 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
这应该确保您的插入物只有一致性。但是,当您指定遗留应用程序也在使用此表时,您是否可以确定在插入新记录时它不会与此冲突?