web-dev-qa-db-fra.com

Quelle est la solution idiomatique dans SQL Server pour réserver un bloc d'ID à utiliser dans une insertion en bloc?

J'ai une table avec une colonne d'identité et je souhaite réserver un bloc d'identifiants que je peux utiliser pour l'insertion en bloc, tout en permettant aux insertions de continuer à se produire dans cette table.

Notez que cela fait partie d'une insertion groupée de plusieurs tables, où ces autres tables se rapportent à ces identifiants via un FK. Par conséquent, je dois les bloquer afin que je puisse préparer les relations à l'avance.

J'ai trouvé une solution qui fonctionne en prenant un verrou sur la table dans une transaction, puis en réamorçant (ce qui est assez rapide). Mais cela me semble un peu hacky - existe-t-il un modèle généralement accepté pour le faire?

create table dbo.test
(
    id bigint not null primary key identity(1,1),
    SomeColumn nvarchar(100) not null
)

Voici le code pour bloquer (faire de la place pour) certains identifiants:

declare @numRowsToMakeRoomFor int = 100

BEGIN TRANSACTION;

        SELECT  MAX(Id) FROM dbo.test WITH (  XLOCK, TABLOCK ) -- will exclusively lock the table whilst this tran is in progress, 
        --another instance of this query will not be able to pass this line until this instance commits

        --get the next id in the block to reserve
        DECLARE @firstId BIGINT = (SELECT IDENT_CURRENT( 'dbo.test' )  +1);

        --calculate the block range
        DECLARE @lastId BIGINT = @firstId + (@numRowsToMakeRoomFor -1);

        --reseed the table
        DBCC CHECKIDENT ('dbo.test',RESEED, @lastId);

COMMIT TRANSACTION;    

select @firstId;

Mon code est un traitement par lots de blocs de données en morceaux d'environ 1000. J'ai environ un milliard de lignes à insérer au total. Tout fonctionne bien - la base de données n'est pas le goulot d'étranglement, le traitement par lots lui-même est coûteux en calcul et me oblige à ajouter quelques serveurs pour fonctionner en parallèle, j'ai donc besoin d'accueillir plus d'un processus "insertion par lots" à la en même temps.

8
Daniel James Bryars

Vous pouvez utiliser la procédure (introduite dans SQL Server 2012):
sp_sequence_get_range

Pour l'utiliser, vous devez créer un objet SEQUENCE et l'utiliser comme valeur par défaut au lieu de la colonne IDENTITY.

Il y a un exemple:

CREATE SCHEMA Test ;  
GO  

CREATE SEQUENCE Test.RangeSeq  
    AS int   
    START WITH 1  
    INCREMENT BY 1  
    CACHE 10  
;

CREATE TABLE Test.ProcessEvents  
(  
    EventID int PRIMARY KEY CLUSTERED   
        DEFAULT (NEXT VALUE FOR Test.RangeSeq),  
    EventTime datetime NOT NULL DEFAULT (getdate()),  
    EventCode nvarchar(5) NOT NULL,  
    Description nvarchar(300) NULL  
) ;


DECLARE 
   @range_first_value_output sql_variant ;  

EXEC sp_sequence_get_range  
@sequence_name = N'Test.RangeSeq'  
, @range_size = 4  
, @range_first_value = @range_first_value_output OUTPUT ;

SELECT @range_first_value_output; 

Documentation: sp_sequence_get_range

13
Piotr