2010-11-18 11 views
5

Si consideri il seguente SQL:Do Inserted Records ricevere sempre Identità Contiguo Valori

CREATE TABLE Foo 
(
    ID int IDENTITY(1,1), 
    Data nvarchar(max) 
) 

INSERT INTO Foo (Data) 
SELECT TOP 1000 Data 
FROM SomeOtherTable 
WHERE SomeColumn = @SomeParameter 

DECLARE @LastID int 
SET @LastID = SCOPE_IDENTITY() 

Vorrei sapere se posso dipendere dai 1000 righe che ho inserito nella tabella Foo avere valori di identità contigui. Nelle parole di ordine, se questo blocco SQL produce un @LastID di 2000, posso sapere con certezza che l'ID del primo record che ho inserito era 1001? Sono principalmente curioso di più affermazioni che inseriscono record nella tabella Foo contemporaneamente.

So che potrei aggiungere una transazione serializzabile attorno alla mia istruzione di inserimento per garantire il comportamento che voglio, ma ho davvero bisogno? Sono preoccupato che l'introduzione di una transazione serializzabile riduca le prestazioni, ma se SQL Server non consentirà l'inserimento di altre istruzioni nella tabella Foo mentre questa istruzione è in esecuzione, non devo preoccuparmene.

risposta

7

Non sono d'accordo con la risposta accettata. Questo può essere facilmente testato e smentito eseguendo quanto segue.

Setup

USE tempdb 

CREATE TABLE Foo 
(
    ID int IDENTITY(1,1), 
    Data nvarchar(max) 
) 

Collegamento 1

USE tempdb 

SET NOCOUNT ON 
WHILE NOT EXISTS(SELECT * FROM master..sysprocesses WHERE context_info = CAST('stop' AS VARBINARY(128))) 
BEGIN 
INSERT INTO Foo (Data) 
VALUES ('blah') 
END 

Connection 2

USE tempdb 

SET NOCOUNT ON 
SET CONTEXT_INFO 0x 

DECLARE @Output TABLE(ID INT) 

WHILE 1 = 1 
BEGIN 
    /*Clear out table variable from previous loop*/ 
    DELETE FROM @Output 

    /*Insert 1000 records*/ 
    INSERT INTO Foo (Data) 
    OUTPUT inserted.ID INTO @Output 
    SELECT TOP 1000 NEWID() 
    FROM sys.all_columns 

    IF EXISTS(SELECT * FROM @Output HAVING MAX(ID) - MIN(ID) <> 999) 
     BEGIN 
     /*Set Context Info so other connection inserting 
      a single record in a loop terminates itself*/ 
     DECLARE @stop VARBINARY(128) 
     SET @stop = CAST('stop' AS VARBINARY(128)) 
     SET CONTEXT_INFO @stop 

     /*Return results for inspection*/ 
     SELECT ID, DENSE_RANK() OVER (ORDER BY Grp) AS ContigSection 
     FROM 
      (SELECT ID, ID - ROW_NUMBER() OVER (ORDER BY [ID]) AS Grp 
      FROM @Output) O 
     ORDER BY ID 

     RETURN 
     END 
END 
+0

Scoperta molto interessante! Apprezzo che tu abbia messo insieme questo test. Ho anche provato a racchiudere l'istruzione "Insert 1000 records" in una transazione serializzabile, ma senza alcun risultato, ho ancora i record interlacciati. MA, se inserisco "Insert 1000 records" in una transazione serializzabile e chiami SELECT MAX (ID) FROM Foo all'interno della transazione prima del mio inserimento, posso quindi garantire che i miei record saranno contigui. Questa soluzione previene tuttavia inserimenti simultanei e non sono disposto a prendere il colpo di performance che potrebbe causare. –

+1

@John - serializzabile su un heap acquisirà solo un esclusivo lock da tavolo, penso che tu possa ottenere lo stesso effetto con un semplice suggerimento di blocco. 'WITH (TABLOCKX)' Come dici tu, questo avrà un impatto sulla concorrenza. –

+0

mio errore, nel test ero in esecuzione ho aggiunto un PK alla colonna ID sul tavolo Foo, dal momento che più da vicino rappresentava la mia situazione. –

6

Sì, saranno contigui perché l'INSERISMO è atomico: completo successo o rollback completo. Essa viene eseguita anche come una singola unità di lavoro: si voleva ottenere qualsiasi "interleaving" con altri processi

Tuttavia (! O per mettere la vostra mente a riposo), considerano il OUTPUT clause

DECLARE @KeyStore TABLE (ID int NOT NULL) 

INSERT INTO Foo (Data) 
OUTPUT INSERTED.ID INTO @KeyStore (ID) --this line 
SELECT TOP 1000 Data 
FROM SomeOtherTable 
WHERE SomeColumn = @SomeParameter 
+0

+1 concordato - all'interno di questa transazione, saranno contigui - ma nel corso della vita del tavolo, nessuna garanzia sono fatti - ci potrebbe essere lacune –

+0

Ci dispiace, ma mi attraversava @ test di Martin e in effetti mostra che i record possono essere interlacciati nella tabella al centro di un inserto. Sono sicuro che la tua affermazione che l'INSERTO sia atomico è ancora valida, ma sembra che ciò non significhi che le righe possano essere intercalate. –

+0

Come risulta, anche se la tua risposta è sbagliata sulla parte contigua, ho comunque finito con l'uso di OUTPUT ... INTO per risolvere il mio problema secondo il tuo consiglio e @ KM. –

3

Se si desidera i valori di identità per utilizzare più righe USCITA:

DECLARE @NewIDs table (PKColumn int) 
INSERT INTO Foo (Data) 
    OUTPUT INSERTED.PKColumn 
    INTO @NewIDs 
SELECT TOP 1000 Data 
FROM SomeOtherTable 
WHERE SomeColumn = @SomeParameter 

ora avete l'intero set di valori nella tabella @NewIDs. Puoi aggiungere qualsiasi colonna dalla tabella Foo alla tabella @NewIDs e inserire anche quelle colonne.

1

Non è una buona pratica attribuire qualsiasi tipo di significato ai valori di identità. Dovresti dare per scontato che non siano altro che interi garantiti come unici nell'ambito della tua tabella.

+0

Sono propenso a concordare con te in teoria, ma in pratica questo potrebbe non essere un problema. Personalmente cercherò una soluzione più robusta. – MikeAinOz

0

Prova ad aggiungere il seguente:

option(maxdop 1)

+1

Hai eseguito il test di @ Martin su un computer multi-core con questa opzione? Sarei interessato a sentire i risultati. –