L'impostazione IDENTITY_INSERT ON
da sola non elimina la concorrenza - questo non pone alcun blocco esclusivo sulla tabella, ma solo un breve blocco di stabilità dello schema (Sch-S).
Quindi cosa potrebbe teoricamente accadere, in base al comportamento predefinito, è che potresti farlo nella sessione 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;
In un'altra sessione, è possibile inserire righe nella tabella ai punti 1, 2, 3 o 4. Questo può sembrare una buona cosa, tranne ciò che accade per qualsiasi inserimento che si verifica tra 2 e 3, è che il valore generato automaticamente attivato da un'altra sessione si basa sui risultati dell'istruzione 2 - quindi genererà un 101 e quindi l'istruzione 3 avrà esito negativo con una violazione della chiave primaria. Questo è abbastanza semplice da configurare e testare te stesso con alcuni 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 volta avviato quel batch, avvia questo batch in un'altra finestra:
-- session 2
INSERT dbo.what DEFAULT VALUES;
WAITFOR DELAY '00:00:01';
GO 20
La sessione 2 dovrebbe sempre inserire valori compresi tra 1 e 20, giusto? Tranne che poiché l'identità sottostante è stata aggiornata dal manuale inserisce la sessione 1, ad un certo punto la sessione 2 riprenderà da dove era stata interrotta la sessione 1, e inserirà 32, o 33, o 34 ecc. Sarà permesso farlo, ma quindi la sessione 1 fallirà al successivo inserto con una violazione di PK (che si vince potrebbe essere solo una questione di tempistica).
Un modo per ovviare a questo è quello di invocare a TABLOCK
sul primo inserto:
INSERT dbo.what WITH (TABLOCK) (id) VALUES(32);
Questo bloccherà tutti gli altri utenti che provano a inserire (o fare qualsiasi cosa, in realtà) con questa tabella fino a quando non hai finito di spostare indietro quelle righe archiviate. Ciò limita la concorrenza, certo, ma è così che vuoi che funzioni il blocco. E spero che questo non sia qualcosa che sta accadendo a un ritmo così frequente in cui blocchi continuamente altre persone.
Un paio di altre soluzioni alternative:
- smetti di preoccuparti del
IDENTITY
valore generato. Che importa? Utilizzare un UNIQUEIDENTIFIER
(forse generato in una tabella separata con un IDENTITY
surrogato) se il valore originale è molto importante.
- modifica il processo di archiviazione per utilizzare un "soft delete" in cui qualcosa è contrassegnato inizialmente come archiviato e l'archiviazione non è resa permanente fino a qualche data successiva. Quindi qualunque processo tenti di spostarli indietro può semplicemente eseguire un aggiornamento diretto e correggere il flag di eliminazione graduale.