Nieoczekiwane luki w kolumnie TOŻSAMOŚĆ

18

Próbuję wygenerować unikalne numery zamówień zakupu, które zaczynają się od 1 i zwiększają o 1. Mam tabelę PONumber utworzoną za pomocą tego skryptu:

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)    
);

I procedura składowana utworzona za pomocą tego skryptu:

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

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

W momencie tworzenia działa to dobrze. Po uruchomieniu procedura przechowywana rozpoczyna się od żądanej liczby i zwiększa się o 1.

Dziwne jest to, że jeśli wyłączę lub przejdę w stan hibernacji komputera, to przy następnym uruchomieniu procedury sekwencja zwiększy się o prawie 1000.

Zobacz wyniki poniżej:

Numery PO

Widać, że liczba wzrosła z 8 do 1002!

  • Dlaczego to się dzieje?
  • Jak mogę się upewnić, że liczby nie są pomijane w ten sposób?
  • Potrzebuję tylko, aby SQL wygenerował liczby, które są:
    • a) Gwarantowany niepowtarzalny.
    • b) przyrost o żądaną kwotę.

Przyznaję, że nie jestem ekspertem od SQL. Czy źle rozumiem, co robi SCOPE_IDENTITY ()? Czy powinienem stosować inne podejście? Sprawdziłem sekwencje w SQL 2012+, ale Microsoft twierdzi, że domyślnie nie ma gwarancji, że będą unikalne.

Ege Ersoz
źródło

Odpowiedzi:

25

Jest to znany i oczekiwany problem - sposób, w jaki SQL Server zarządza kolumnami TOŻSAMOŚCI, zmienił się w SQL Server 2012 ( część tła ); domyślnie buforuje 1000 wartości, a jeśli zrestartujesz SQL Server, uruchomisz ponownie serwer, przełączysz w tryb awaryjny itp., będzie musiał wyrzucić te 1000 wartości, ponieważ nie będzie miał wiarygodnego sposobu, aby dowiedzieć się, ile z nich faktycznie było wydany. Jest to udokumentowane tutaj . Istnieje flaga śledzenia, która zmienia to zachowanie tak, że każde przypisanie TOŻSAMOŚCI jest rejestrowane *, zapobiegając tym konkretnym lukom (ale nie lukom w przywracaniu lub usuwaniu); Należy jednak zauważyć, że może to być dość kosztowne z punktu widzenia wydajności, więc nawet nie wspomnę tutaj o konkretnej fladze śledzenia.

* (Osobiście uważam, że jest to problem techniczny, który można rozwiązać inaczej, ale ponieważ nie piszę silnika, nie mogę tego zmienić).

Aby wyjaśnić, jak działa TOŻSAMOŚĆ i SEKWENCJA:

  • Żadne z nich nie jest unikalne (musisz wymusić to na poziomie tabeli, używając klucza podstawowego lub unikalnego ograniczenia)
  • Żadna z nich nie jest gwarantowana bez przerw (na przykład jakiekolwiek wycofanie lub usunięcie spowoduje powstanie luki, pomimo tego konkretnego problemu)

Unikalność jest łatwa do wyegzekwowania. Unikanie luk nie jest. Musisz określić, jak ważne jest unikanie tych luk (teoretycznie nie powinieneś przejmować się lukami, ponieważ wartości TOŻSAMOŚĆ / SEKWENCJA powinny być pozbawionymi znaczenia kluczami zastępczymi). Jeśli jest to bardzo ważne, nie powinieneś używać żadnej z implementacji, a raczej rzucić własny generowalny generator sekwencji (zobacz kilka pomysłów tutaj , tutaj i tutaj ) - pamiętaj, że zabije to współbieżność.

Wiele informacji o tym „problemie”:

Aaron Bertrand
źródło
Ta odpowiedź (oprócz części „flaga śledzenia”) dotyczy także większości innych baz danych SQL (tych, które i tak mają sekwencje).
mustaccio,
Dziękuję za odpowiedź. Wyjątkowość jest najważniejszym wymogiem. Luki nie są duże, o ile nie są duże. np. przejście od 1 do 4 byłoby dopuszczalne, ale od 4 do 1003 nie.
Ege Ersoz
1
Wersja skrócona: wartości identyfikatora będą używane jako numery zamówień zakupu. Klient uruchamia raporty miesięczne i chce szybko określić liczbę zamówień złożonych w danym miesiącu, patrząc tylko na numer zamówienia. Nie możemy więc zwiększyć tego o ~ 1000 (co tydzień konserwacja polega na tym, że wszystkie serwery, w tym serwer DB, są restartowane).
Ege Ersoz,
3
Dlaczego nie podasz im bardzo łatwego raportu, który po prostu używa ROW_NUMBER () OVER (PARTITION BY Month ORDER BY ID)? Ponownie, numer identyfikacyjny powinien być bez znaczenia, to okropny sposób na sprawdzenie, ile zamówień zostało przyjętych. Co zrobić, jeśli masz w kodzie błąd, który usuwa 1000 wierszy lub wycofuje 275 transakcji, lub 500 zamówień jest legalnie anulowanych?
Aaron Bertrand
1
@Ege: „... powiedz ile… po prostu patrząc na numer zamówienia”. Twoi użytkownicy będą rozczarowani. Wartości tożsamości po prostu nie działają w ten sposób, ani nie powinieneś (lub oni) przyjmować takich założeń. Wyjątkowy? Tak. Kolejny? Nie. Prawidłowym sposobem zliczania PO przesłanych w ciągu miesiąca jest ... policzenie liczby PO zgłoszonych w tym miesiącu, na podstawie pewnego [niezmiennego] pola Data w każdym rekordzie.
Phill W.
-4

To jest problem SQL Servera. Wszystko, co możesz zrobić, to ponownie ustawić kolumnę.

usuń wpisy z niewłaściwym identyfikatorem kolumny. Ponownie sprawdź tożsamość kolumny. A następnie następny wpis ma odpowiedni identyfikator.

Reseed Identity przy użyciu następującego polecenia sql: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 to ostatni poprawny identyfikator

użytkownik190684
źródło
1
Co masz na myśli mówiąc „usuń wpisy”?
ypercubeᵀᴹ
2
Hmmm .. wydaje się, że usunięcie wpisów może prowadzić do utraty danych.
Michael Green