Was ist die idiomatische Lösung in SQL Server zum Reservieren eines ID-Blocks zur Verwendung in einer Masseneinfügung?

8

Ich habe eine Tabelle mit einer Identitätsspalte und möchte einen Block von IDs reservieren, die ich zum Masseneinfügen verwenden kann, während Einfügungen in diese Tabelle weiterhin möglich sind.

Beachten Sie, dass dies Teil einer Masseneinfügung mehrerer Tabellen ist, wobei sich diese anderen Tabellen über eine FK auf diese IDs beziehen. Deshalb muss ich sie blockieren, damit ich die Beziehungen vorher vorbereiten kann.

Ich habe eine Lösung gefunden, die funktioniert, indem sie in einer Transaktion die Tabelle sperrt und dann die Neuaussaat durchführt (was ziemlich schnell ist). Aber es sieht für mich ein bisschen hackig aus - gibt es ein allgemein akzeptiertes Muster dafür?

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

Hier ist der Code, um einige IDs auszublenden (Platz zu machen):

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;

Mein Code besteht aus der Stapelverarbeitung von Datenblöcken in Blöcken von ungefähr 1000. Ich muss insgesamt ungefähr eine Milliarde Zeilen einfügen. Alles funktioniert einwandfrei - die Datenbank ist nicht der Flaschenhals, die Stapelverarbeitung selbst ist rechenintensiv und erfordert, dass ich ein paar Server hinzufüge, um parallel ausgeführt zu werden. Daher muss ich mehr als einen Prozess "Batch Inserting" am gleiche Zeit.

Daniel James Bryars
quelle
2
Warum nicht INSERT .. SELECT ..mit verwenden OUTPUT? Kein Verriegeln, kein Nachsaaten. Fügen Sie einfach INSERT ein und holen Sie sich die IDs von OUTPUT, damit Sie sie in den anderen Tabellen verwenden können.
Ypercubeᵀᴹ
@ ypercubeᵀᴹ Entschuldigung, ich sehe, ich wusste nicht, dass Sie Bulk Insert mit Ausgabe können. Ich mache die Masseneinfügung aus Code (c #), der eine Remoteverbindung herstellt. Ich denke, ich könnte Dateien ausschreiben und dann Masseneinfügung aus einer Datei verwenden. Dies könnte etwas schwierig sein.
Daniel James Bryars
3
Was ist das teure Erstellen / Berechnen der Zeilen? Oder haben Sie die Daten bereits in Dateien und müssen sie nur noch einfügen? Außerdem: Wie viele Tabellen und welche Beziehungen bestehen? Nur 2 Tabellen mit einem FK, viele Tabellen mit einem Sternschema, viele Tabellen mit komplexem Schema (Zyklen, mehrere Pfade usw.)?
Ypercubeᵀᴹ

Antworten:

13

Sie können die in SQL Server 2012 eingeführte Prozedur verwenden:
sp_sequence_get_range

Um es zu verwenden, müssen Sie ein SEQUENCE-Objekt erstellen und es als Standardwert anstelle der IDENTITY-Spalte verwenden.

Es gibt ein Beispiel:

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; 

Dokumentation: sp_sequence_get_range

Piotr
quelle