Unerwartete Lücken in der IDENTITY-Spalte


16

Ich versuche, eindeutige Bestellnummern zu generieren, die bei 1 beginnen und um 1 erhöht werden. Ich habe eine PONumber-Tabelle mit diesem Skript erstellt:

CREATE TABLE [dbo].[PONumbers]
(
  [PONumberPK] [int] IDENTITY(1,1) NOT NULL,
  [NewPONo] [bit] NOT NULL,
  [DateInserted] [datetime] NOT NULL DEFAULT GETDATE(),
  CONSTRAINT [PONumbersPK] PRIMARY KEY CLUSTERED ([PONumberPK] ASC)    
);

Und eine gespeicherte Prozedur, die mit diesem Skript erstellt wurde:

CREATE PROCEDURE [dbo].[GetPONumber] 
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO [dbo].[PONumbers]([NewPONo]) VALUES(1);
    SELECT SCOPE_IDENTITY() AS PONumber;
END

Zum Zeitpunkt der Erstellung funktioniert dies einwandfrei. Wenn die gespeicherte Prozedur ausgeführt wird, beginnt sie mit der gewünschten Nummer und wird um 1 erhöht.

Das Seltsame ist, dass, wenn ich meinen Computer ausschalte oder in den Ruhezustand versetze, die Sequenz beim nächsten Ausführen um fast 1000 Sekunden vorgerückt ist.

Siehe Ergebnisse unten:

PO-Nummern

Sie können sehen, dass die Zahl von 8 auf 1002 gesprungen ist!

  • Warum passiert dies?
  • Wie stelle ich sicher, dass keine Zahlen übersprungen werden?
  • Alles, was ich brauche, ist, dass SQL Zahlen generiert, die:
    • a) Garantiert einzigartig.
    • b) um den gewünschten Betrag erhöhen.

Ich gebe zu, ich bin kein SQL-Experte. Verstehe ich falsch, was SCOPE_IDENTITY () tut? Sollte ich einen anderen Ansatz verwenden? Ich habe mir Sequenzen in SQL 2012+ angesehen, aber Microsoft gibt an, dass diese standardmäßig nicht eindeutig sind.

Antworten:


23

Dies ist ein bekanntes und zu erwartendes Problem. Die Art und Weise, wie IDENTITY-Spalten von SQL Server verwaltet werden, hat sich in SQL Server 2012 geändert ( einige Hintergrundinformationen ). Standardmäßig werden 1000 Werte zwischengespeichert, und wenn Sie SQL Server neu starten, den Server neu starten, ein Failover durchführen usw., müssen diese 1000 Werte verworfen werden, da nicht zuverlässig ermittelt werden kann, wie viele davon tatsächlich vorhanden sind problematisch. Dies ist hier dokumentiert . Es gibt ein Ablaufverfolgungsflag, das dieses Verhalten dahingehend ändert, dass jede IDENTITY-Zuweisung protokolliert wird *, um diese spezifischen Lücken zu verhindern (jedoch keine Lücken durch Rollbacks oder Löschvorgänge). Es ist jedoch wichtig zu beachten, dass dies in Bezug auf die Leistung sehr kostspielig sein kann. Daher werde ich hier nicht einmal auf das spezielle Ablaufverfolgungsflag eingehen.

* (Persönlich denke ich, dass dies ein technisches Problem ist, das anders gelöst werden könnte, aber da ich die Engine nicht schreibe, kann ich das nicht ändern.)

Um zu verdeutlichen, wie IDENTITY und SEQUENCE funktionieren:

  • Beides ist garantiert nicht eindeutig (dies muss auf Tabellenebene mithilfe eines Primärschlüssels oder einer eindeutigen Einschränkung erzwungen werden).
  • Weder ist garantiert lückenlos (jedes Zurücksetzen oder Löschen, zum Beispiel, wird eine Lücke erzeugen, ungeachtet dieses spezifischen Problems).

Die Eindeutigkeit lässt sich leicht durchsetzen. Lücken zu vermeiden geht nicht. Sie müssen festlegen, wie wichtig es ist, diese Lücken zu vermeiden (theoretisch sollten Sie sich überhaupt nicht um Lücken kümmern, da IDENTITY / SEQUENCE-Werte bedeutungslose Ersatzschlüssel sein sollten). Wenn es sehr wichtig ist, sollten Sie keine der beiden Implementierungen verwenden, sondern Ihren eigenen serialisierbaren Sequenzgenerator rollen (siehe einige Ideen hier , hier und hier ).

Viel Hintergrund zu diesem "Problem":


Diese Antwort (mit Ausnahme des Teils "Ablaufverfolgungsflag") gilt auch für die meisten anderen SQL-Datenbanken (die ohnehin Sequenzen aufweisen).
Mustaccio

Danke für die Antwort. Einzigartigkeit ist die wichtigste Einzelanforderung. Lücken sind keine große Sache, solange sie nicht groß sind. zB von 1 auf 4 zu gehen wäre akzeptabel, aber von 4 auf 1003 nicht.
Ege Ersoz

1
Kurzversion: Die ID-Werte werden als Bestellnummern verwendet. Der Kunde erstellt monatliche Berichte und möchte anhand der Bestellnummer schnell feststellen können, wie viele Bestellungen in diesem Monat eingereicht wurden. Wir können es also nicht um ~ 1000 inkrementieren lassen (es gibt eine wöchentliche Wartung, bei der alle Server, einschließlich des DB-Servers, neu gestartet werden).
Ege Ersoz,

3
Warum geben Sie ihnen keinen sehr einfachen Bericht, der nur ROW_NUMBER () OVER (PARTITION NACH Monat ORDER BY ID) verwendet? Auch hier sollte die ID-Nummer bedeutungslos sein, das ist ein schrecklicher Weg, um zu sehen, wie viele Bestellungen aufgenommen wurden. Was ist, wenn Sie einen Fehler in Ihrem Code haben, der 1000 Zeilen löscht oder 275 Transaktionen rückgängig macht, oder 500 Bestellungen zu Recht storniert werden?
Aaron Bertrand

1
@Ege: "... sagen Sie, wie viele ... nur anhand der Bestellnummer". Ihre Benutzer werden enttäuscht sein. Identitätswerte funktionieren einfach nicht so, noch sollten Sie (oder sie) eine solche Annahme machen. Einzigartig? Ja. Aufeinanderfolgenden? Nein. Die korrekte Methode zum Zählen der während eines Monats gesendeten Bestellungen besteht darin, die Anzahl der in diesem Monat gesendeten Bestellungen zu zählen, basierend auf einem [unveränderlichen] Datumsfeld in jedem Datensatz.
Phill W.

-4

Dies ist ein Problem von SQL Server. Alles, was Sie tun können, ist die Säule neu zu säen.

Löschen Sie die Einträge mit der falschen Spalten-ID. Erneuern Sie die Spaltenidentität. Und dann hat der nächste Eintrag die richtige ID.

Reseed-Identität mit dem folgenden SQL-Befehl: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 ist die letzte korrekte ID


1
Was meinst du mit "Einträge löschen"?
Ypercubeᵀᴹ

2
Hmmm ... das Löschen von Einträgen kann zu Datenverlust führen.
Michael Green
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.