Answers:
IDENTITY_INSERT ON
单独设置不会消除并发性-这不会在表上放置任何排他锁,而只是简短的架构稳定性(Sch-S)锁。
因此,从理论上讲,在默认行为下,您可以在会话1中进行此操作:
BEGIN TRANSACTION;
-- 1
SET IDENTITY_INSERT dbo.tablename ON;
-- 2
INSERT dbo.tablename(id, etc) VALUES(100, 'foo'); -- next identity is now 101
-- 3
INSERT dbo.tablename(id, etc) VALUES(101, 'foo'); -- next identity is now 102
-- 4
SET IDENTITY_INSERT dbo.tablename OFF;
COMMIT TRANSACTION;
在另一个会话中,您可以在表的第1、2、3或4点处插入行。这似乎是一件好事,除了2到3之间发生的任何插入会发生的情况是触发了自动生成的值另一个会话基于语句2的结果-因此它将生成101,然后语句3将因主键冲突而失败。设置和测试自己非常简单WAITFOR
:
-- session 1
-- DROP TABLE dbo.what;
CREATE TABLE dbo.what(id INT IDENTITY PRIMARY KEY);
GO
BEGIN TRANSACTION;
SET IDENTITY_INSERT dbo.what ON;
INSERT dbo.what(id) VALUES(32);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(33);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(34);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(35);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(36);
SET IDENTITY_INSERT dbo.what OFF;
COMMIT TRANSACTION;
该批处理开始后,请在另一个窗口中启动该批处理:
-- session 2
INSERT dbo.what DEFAULT VALUES;
WAITFOR DELAY '00:00:01';
GO 20
会话2应该只插入1到20之间的值,对吧?除非由于您的手动插入会话1已更新了基础标识,否则会话2将在会话1中断的地方继续进行,然后插入32或33或34等。这是允许的,但是那么会话1将在下一次插入时因PK违规而失败(一次获胜可能只是时间问题)。
解决此问题的一种方法是TABLOCK
在第一个插入上调用一个:
INSERT dbo.what WITH (TABLOCK) (id) VALUES(32);
这将阻止任何其他试图向该表插入(或实际上执行任何操作)的用户,直到您完成将这些归档行移回为止。当然,这会限制并发,但这是您希望阻止工作的方式。希望这不会以如此频繁的速度发生,而您却一直在阻止其他人。
其他一些解决方法:
IDENTITY
产生的价值。谁在乎?如果原始值非常重要,请使用UNIQUEIDENTIFIER
(可能是在一个单独的表中生成,并带有IDENTITY
一个代理)。