2

Ich habe, was auf den ersten Blick schien ein sehr einfaches Problem. Ich möchte in der Lage sein, einen eindeutigen Schlüsselwert mit einem Präfix zu erhalten. Ich habe eine Tabelle, die die Spalten 'Prefix' und 'Next_Value' enthält.Wie Transaktionsintegrität in SQL Server 2005 garantiert werden

Sie denken also, Sie starten einfach eine Transaktion, holen den nächsten Wert aus dieser Tabelle, inkrementieren den nächsten Wert in der Tabelle und commit, verketten das Präfix mit dem Wert, und Sie erhalten eine Reihe von eindeutigen alphanumerischen Schlüsseln .

Allerdings unter Last, mit verschiedenen Servern, die diese gespeicherte Proc über ADO.NET treffen, habe ich entdeckt, dass es von Zeit zu Zeit den gleichen Schlüssel an verschiedene Clients zurückgibt. Dies führt natürlich später zu einem Fehler, wenn der Schlüssel als Primärschlüssel verwendet wird!

Ich hatte naiv angenommen BEGIN TRAN ... COMMIT TRAN sicherte die Atomizität der Datenzugriffe innerhalb des Bereichs. Als ich das betrachtete, entdeckte ich Transaktionsisolationsebenen und fügte SERIALIZABLE als das restriktivste hinzu - ohne Freude.

Create proc [dbo].[sp_get_key] 
    @prefix nvarchar(3) 
    as 
    set tran isolation level SERIALIZABLE 
    declare  @result nvarchar(32) 

    BEGIN TRY 
     begin tran 

     if (select count(*) from key_generation_table where prefix = @prefix) = 0 begin 
     insert into key_generation_table (prefix, next_value) values (@prefix,1) 
     end 

     declare @next_value int 

     select @next_value = next_value 
     from key_generation_table 
     where prefix = @prefix 

     update key_generation_table 
     set next_value = next_value + 1 
     where prefix = @prefix 

     declare @string_next_value nvarchar(32) 
     select @string_next_value = convert(nvarchar(32),@next_value) 

     commit tran 

     select @result = @prefix + substring('000000000000000000000000000000',1,10-len(@string_next_value)) + @string_next_value 

     select @result 

    END TRY 
    BEGIN CATCH 
     IF @@TRANCOUNT > 0 ROLLBACK TRAN 

     DECLARE @ErrorMessage NVARCHAR(400); 
     DECLARE @ErrorNumber INT; 
     DECLARE @ErrorSeverity INT; 
     DECLARE @ErrorState INT; 
     DECLARE @ErrorLine INT; 

     SELECT @ErrorMessage = N'{' + convert(nvarchar(32),ERROR_NUMBER()) + N'} ' + N'%d, Line %d, Text: ' + ERROR_MESSAGE(); 
     SELECT @ErrorNumber = ERROR_NUMBER(); 
     SELECT @ErrorSeverity = ERROR_SEVERITY(); 
     SELECT @ErrorState = ERROR_STATE(); 
     SELECT @ErrorLine = ERROR_LINE(); 
     RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine) 


    END CATCH 

Hier ist der Schlüssel Generation Tisch ...

CREATE TABLE [dbo].[Key_Generation_Table](
     [prefix] [nvarchar](3) NOT NULL, 
     [next_value] [int] NULL, 
    CONSTRAINT [PK__Key_Generation_T__236943A5] PRIMARY KEY CLUSTERED 
    (
    [prefix] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,  
     ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
+0

Welcher Teil des Schlüssels ist dupliziert? Vorwahl oder Nummer Teil? – gbn

Antwort

1

Paar von Dingen, die Sie haben eine Wettlaufbedingung auf Ihrem if-Block. Zwei Anfragen kommen zur selben Zeit für ein neues Präfix, beide könnten den if-Block passieren. Sie sollten dies ändern, um immer in Ihre Tabelle einzufügen, aber in Ihrer where-Klausel für die Einfügung machen Sie die Überprüfung, um sicherzustellen, dass sie nicht existiert. Außerdem würde ich die Verwendung von Exists anstelle von count (*) = 0 empfehlen. Mit Existiert, sobald SQL eine Zeile findet, kann es aufhören zu suchen.

Dieselbe Sache kann mit Ihrer Auswahl passieren, Sie könnten zwei Threads haben beide den gleichen Wert auswählen, dann wird man blockiert warten auf das Update, aber dann, wenn es zurückgibt, wird es die alte ID zurückgeben.

Ihre Logik Ändern Sie die Zeile zuerst zu aktualisieren, erhalten dann wird der Wert Sie es auch aktualisiert

update key_generation_table   
    set next_value = next_value + 1 
    where prefix = @prefix 

     select @next_value = next_value -1   
     from key_generation_table   
     where prefix = @prefix   

ich auch mit der ouput Anweisung aussehen würde, anstatt die zweite Auswahl zu tun.

EDIT

Ich würde probally dies ändern Ausgabe seit yoru auf SQL2005 zu verwenden:

declare @keyTable as table (next_value int) 

UPDATE key_generation_Table 
set next_value=next_value+1 
OUTPUT DELETED.next_value into @keyTable(next_value) 
WHERE [email protected] 

/* Update the following to use your formating */ 
select next_value from @keyTable 
+0

Sie haben es geschafft. Danke Josh !! – rc1

+0

Willkommen vergessen Sie nicht, Ihre Insert-Anweisung zu beheben, die Sie eines Tages auch beißen werden – JoshBerke

+0

Ich hatte nie gesehen, dass OUTPUT DELETED Sache ... sehr interessant. Danke – rc1

0

Denken außerhalb der Box, könnten Sie einfach eine Zeile in eine Tabelle mit einer AUTO_INCREMENT-ID hinzufügen und dann die ID verwenden? Unter Last ist das garantiert einzigartig. Sie könnten dann die Zeile löschen (damit die Tabelle nicht endlos wächst).

Um Ihre Frage zu beantworten, was passiert, transactions aren't critical regions.

SERIALIZABLE

restriktivsten Isolationsstufe. Wenn es verwendet wird, können die Phantomwerte nicht auftreten. Es verhindert, dass andere Benutzer Zeilen in den Datensatz aktualisieren oder einfügen, bis die Transaktion abgeschlossen ist.

Das Problem, das durch diesen Mechanismus verhindert werden soll, unterscheidet sich von dem, dem Sie ausgesetzt sind.

Wenn Sie mit dem oben beschriebenen Ansatz fortfahren möchten, sollten Sie eine exklusive Sperre für die kritische Region erhalten.

+0

Ich würde lieber verstehen, was hier vor einer Arbeitsumgebung vor sich geht. – rc1

+0

Ich werde dann eine Erklärung hinzufügen. – MarkusQ

+0

Danke - geschätzt! – rc1

1

Versuchen mit UPDLOCK anspielend.

key_generation_table wird idealerweise nur mit diesem spezifischen gespeicherten Prozess verwendet. Andernfalls kann UPDLOCK die Wahrscheinlichkeit von Deadlocks erhöhen.

+0

Danke, aber das hat das Problem nicht behoben – rc1

0

Serializable hält Sperren, aber Lesevorgänge sind erlaubt. So kann das select/update in der Mitte unter Last dasselbe Ergebnis liefern, wenn der proc sehr schnell parallel aufgerufen wird. Ich denke ...

Wenn Sie es so tun, mit der gültigen Syntax, können Sie die 2. kombinieren Die Tablock sorgt dafür, dass die gesamte Tabelle gesperrt ist. Dies ist anders als serialisierbar, was Parallelität ist. Tablock ist Granularität. Sie gehen auch davon aus, dass der Schlüssel vorhanden ist. Fügen Sie nach Bedarf das fehlende Präfix hinzu.

update 
    key_generation_table WITH (TABLOCK) 
set 
    @next_value = next_value, next_value = next_value + 1 
where 
    prefix = @prefix 

if @@ROWCOUNT = 0 
begin 
    set @next_value = 1 
    insert into key_generation_table (prefix, next_value) values (@prefix, 1) 
end 
select @string_next_value = convert(nvarchar(32),@next_value)