2010-06-07 11 views
7

Diese Frage gilt sicherlich für einen viel breiteren Bereich, aber hier ist es.Richtiger Weg zum Erstellen von Bestellnummern in SQL Server

Ich habe eine grundlegende E-Commerce-App, wo Benutzer natürlich Bestellungen aufgeben können. Diese Befehle müssen eine eindeutige Nummer haben, die ich gerade erzeugen möchte.

Jede Bestellung ist herstellerspezifisch. Grundsätzlich habe ich eine OrderNumberInfo (VendorID, OrderNumber) Tabelle. Jetzt, wenn ein Kunde eine Bestellung aufgibt, muss ich OrderNumber für einen bestimmten Verkäufer erhöhen und diesen Wert zurückgeben. Natürlich, ich will nicht andere Prozesse mit mir stören, so muss ich nur diese Zeile sperren irgendwie:

begin tranaction 

    declare @n int 
    select @n = OrderNumber 
     from OrderNumberInfo 
     where VendorID = @vendorID 

    update OrderNumberInfo 
     set OrderNumber = @n + 1 
     where OrderNumber = @n and VendorID = @vendorID 

commit transaction 

Jetzt habe ich über select ... with (updlock rowlock), pessimistische Sperren lesen usw., sondern kann einfach nicht passen All dies in einem kohärenten Bild:

  • Wie spielen diese Hinweise mit Snapshot-Isolation von SQL Server 2008?
  • Führen sie Sperren auf Zeilen-, Seiten- oder sogar Tabellenebene aus?
  • Wie toleriert dies mehrere Benutzer, die versuchen, Zahlen für einen einzelnen Anbieter zu generieren?
  • Welche Isolationsstufen sind hier geeignet?
  • Und in der Regel - was ist der Weg, solche Dinge zu tun?

EDIT

Nur wenige Dinge klarer zu machen:

  • Leistung in dieser besonderen Ecke der App ist absolut kein Problem: Aufträge relativ selten gestellt werden und wird ein verwickeln teurer Aufruf an den Web-Service der Anbieter, so ist 1-Sekunden-Verzögerung ziemlich tolerabel
  • Wir wirklich müssen die Bestellnummern jedes Lieferanten zu b haben e unabhängig und sequentiell

Antwort

3

Sie könnten eine OUTPUT Klausel verwenden. Dies sollte alles atomar tun, ohne eine Transaktion zu erfordern.

-- either return the order number directly as a single column resultset 
UPDATE OrderNumberInfo 
SET OrderNumber = OrderNumber + 1 
    OUTPUT DELETED.OrderNumber 
WHERE VendorID = @vendorID 


-- or use an intermediate table variable to get the order number into @n 
DECLARE @n INT 
DECLARE @temp TABLE (OrderNumber INT) 

UPDATE OrderNumberInfo 
SET OrderNumber = OrderNumber + 1 
    OUTPUT DELETED.OrderNumber 
    INTO @temp (OrderNumber) 
WHERE VendorID = @vendorID 

SET @n = (SELECT TOP 1 OrderNumber FROM @temp) 

Die obigen Beispiele gehen davon aus, dass die VendorID Spalte eine eindeutige Einschränkung hat, oder zumindest, dass es dann nur eine Zeile pro Hersteller-ID sein. Wenn das nicht der Fall ist, werden Sie möglicherweise mehrere Zeilen aktualisieren und/oder zurückgeben, was nicht als eine gute Idee erscheint!

+0

Das ist interessant! Vielen Dank! –

+0

Wenn die OrderNumber-Spalte die zuletzt verwendete OrderNumber darstellt, möchten Sie dann nicht "Insert.OrderNumber" verwenden? – Thomas

+0

@Thomas: Ich imitiert nur das Verhalten des Codes in der Frage, die zuerst die Bestellnummer erhält und dann die Spalte erhöht. Aber wenn Sie inkrementieren möchten und dann die inkrementierte Bestellnummer bekommen, dann wäre 'INSERTED.OrderNumber' der richtige Weg. – LukeH

4

Ihre Lösung führt zu einem potenziellen Leistungsengpass auf der OrderNumberInfo Tabelle.

Gibt es einen bestimmten Grund, warum die Aufträge nicht einfach eine Identitätsspalte sein können, möglicherweise mit einer Hersteller-ID auf der Anwendungsseite (z. B. MSFT-232323) vorangestellt?

Der einzige Nachteil dieses Ansatzes ist, dass pro-Vendor-Bestellungen kein Muster "Add-1-to-Get-Next-Order- #" sind, aber ich bin mir keiner technischen oder geschäftlichen Betrachtung bewusst Warum würde das ein Problem darstellen, obwohl es die Sequenzverarbeitung etwas komplizierter machen könnte?

Sie würden immer noch inkrementiert und einzigartig pro Anbieter sein, was die einzige wirkliche Voraussetzung für eine Auftrags-ID ist.

Es wird natürlich den zusätzlichen Vorteil einer sehr einfachen, herstellerunabhängigen Logik haben, vorausgesetzt, Sie haben überhaupt welche) - wie zum Beispiel anwendungsweite QC/Reporting.

+0

Leider ist dies die Voraussetzung: Jeder Hersteller muss eine unabhängige Sequenz haben. –

+2

@Anton - ok ... tut mir leid für die Dichte, aber was ist der Ursprung/Geschäftsgrund für diese Anforderung? – DVK

+2

Stimmen Sie mit DVK überein, Sie tun etwas völlig unnötiges und riskantes. WENN ich dies als Voraussetzung hätte, würde ich zurücktreten und ihnen sagen, dass das Risiko die wahrgenommenen Vorteile überwiegt. Solange jeder Lieferant über eindeutige orderids verfügt, ist alles in Ordnung, es ist nicht so, als würden sie die IDs der anderen sehen, und es gibt buchstäblich keinen vernünftigen Geschäftsgrund, weshalb Bestellungen in der Reihenfolge ihrer Reihenfolge ohne übersprungene Werte ablaufen müssen. Die zusätzlichen Kosten dafür und das zusätzliche Risiko von Datenintegritätsproblemen, wenn Sie es beim ersten Mal nicht richtig machen, sind viel höher als die Verwendung eines Identitätsfeldes, was buchstäblich keinen Nutzen bringt. Keiner. – HLGEM

1

Ich benutze normalerweise etwas wie folgt aus:

update OrderNumberInfo with (rowlock) 
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1 
where VendorID = @VendorID 

Es ist nicht in einer Transaktion eingewickelt werden muss. In der Tat, wenn Sie es in eine Transaktion umbrechen, wird SQL Server Sperren für die Tabelle halten und alles verlangsamen. Wenn ich solche Dinge in einem Web-Service machen muss, führe ich es immer in einer separaten Datenbankverbindung außerhalb jeder Transaktion aus, die zu der Zeit geöffnet sein könnte, nur um sicherzugehen.

Ich glaube (aber nicht bewiesen), dass SQL Server ein Latch anstelle einer Transaktion verwendet, um es atomar zu machen, die effizienter sein sollte.

Wenn Ihre Tabelle Design so ist, dass der Verkäufer Reihe nach Bedarf erstellt werden muss, wenn es nicht, dann existiert diese Logik verwendet stattdessen:

declare @error int, @rowcount int 

-- Attempt to read and update the number. 
update OrderNumberInfo with (rowlock) 
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1 
where VendorID = @VendorID 

select @error = @@error, @rowcount = @@rowcount 
if @error <> 0 begin 
    return @error 
end 

-- If the update succeeded then exit now. 
if @rowcount > 0 begin 
    return 0 
end 

-- Insert the row if it doesn't exist yet. 
insert into OrderNumberInfo (VendorID, OrderNumber) 
select VendorID, 1 
where not exists (select null from OrderNumberInfo where VendorID = @VendorID) 

select @error = @@error 
if @error <> 0 begin 
    return @error 
end 

-- Attempt to read and update the number. 
update OrderNumberInfo with (rowlock) 
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1 
where VendorID = @VendorID 

select @error = @@error 
if @error <> 0 begin 
    return @error 
end 

Dieser Code noch benötigt keine Transaktion, weil jede atomare Anweisung unabhängig davon, wie viele andere Verbindungen den Code gleichzeitig ausführen, erfolgreich ist.

Haftungsausschluss: Ich habe dies ohne Probleme auf SQL Server 7-2005 verwendet. Ich kann noch nicht kommentieren, sein Verhalten im Jahr 2008.

0

Die Art und Weise, dies zu tun Konsistenz zu erhalten:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
BEGIN TRANSACTION 
declare @n int 
select @n = OrderNumber 
    from OrderNumberInfo 
    where VendorID = @vendorID 

update OrderNumberInfo 
    set OrderNumber = @n + 1 
    where OrderNumber = @n and VendorID = @vendorID 

COMMIT TRANSACTION 

Dies wird die strengste Form der Isolation verwenden und kein lustiges Geschäft zu gewährleisten.

0

hier ist es:

deklarieren @C int = 0; Update Tabelle gesetzt Code = @ C, @ C = @ C + 1

Verwandte Themen