La configuración IDENTITY_INSERT ON
por sí sola no elimina la concurrencia: esto no coloca ningún bloqueo exclusivo en la mesa, solo un breve bloqueo de estabilidad del esquema (Sch-S).
Entonces, lo que teóricamente podría suceder, bajo el comportamiento predeterminado, es que podría hacer esto en la sesión 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;
En otra sesión, puede insertar filas en la tabla en los puntos 1, 2, 3 o 4. Esto puede parecer algo bueno, excepto que lo que sucede para cualquier inserción que ocurre entre 2 y 3 es que el valor generado automáticamente se activó otra sesión se basa en los resultados de la declaración 2, por lo que generará un 101, y luego la declaración 3 fallará con una violación de clave principal. Esto es bastante simple de configurar y probarse con algunos WAITFOR
s:
-- 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;
Una vez que ese lote ha comenzado, comience este lote en otra ventana:
-- session 2
INSERT dbo.what DEFAULT VALUES;
WAITFOR DELAY '00:00:01';
GO 20
La sesión 2 solo debe insertar valores del 1 al 20, ¿verdad? Excepto que debido a que la identidad subyacente ha sido actualizada por su sesión 1 de inserciones manuales, en algún momento la sesión 2 retomará donde dejó la sesión 1 e insertará 32, 33 o 34, etc. Se permitirá hacer esto, pero entonces la sesión 1 fallará en la siguiente inserción con una infracción de PK (la que gane puede ser solo cuestión de tiempo).
Una forma de solucionar esto es invocar a TABLOCK
en la primera inserción:
INSERT dbo.what WITH (TABLOCK) (id) VALUES(32);
Esto bloqueará a cualquier otro usuario que intente insertar (o hacer algo, realmente) con esta tabla hasta que haya terminado de mover esas filas archivadas hacia atrás. Esto acelera la concurrencia, claro, pero esta es la forma en que desea que funcione el bloqueo. Y con suerte esto no es algo que esté sucediendo a un ritmo tan frecuente en el que estás bloqueando a otras personas todo el tiempo.
Un par de otras soluciones:
- deja de preocuparte por el
IDENTITY
valor generado. ¿A quien le importa? Utilice un UNIQUEIDENTIFIER
(tal vez generado en una tabla separada con un IDENTITY
como sustituto) si el valor original es muy importante.
- cambie el proceso de archivo para usar una "eliminación suave" donde algo se marca como archivado inicialmente y el archivo no se hace permanente hasta una fecha posterior. Entonces, sea cual sea el proceso que intente moverlos hacia atrás, simplemente puede realizar una actualización directa y corregir el indicador de eliminación suave.