Неожиданные пробелы в столбце IDENTITY

18

Я пытаюсь сгенерировать уникальные номера заказов на покупку, которые начинаются с 1 и увеличиваются на 1. У меня есть таблица PONumber, созданная с помощью этого сценария:

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

И хранимая процедура, созданная с помощью этого скрипта:

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

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

На момент создания это работает нормально. Когда хранимая процедура запускается, она начинается с нужного номера и увеличивается на 1.

Странно то, что если я выключаю или переводю компьютер в спящий режим, то при следующем запуске процедуры последовательность увеличивается почти на 1000.

Смотрите результаты ниже:

Номера телефонов

Вы можете видеть, что число подскочило с 8 до 1002!

  • Почему это происходит?
  • Как я могу убедиться, что номера не пропускаются так?
  • Все, что мне нужно, это чтобы SQL генерировал числа, которые:
    • а) Гарантированно уникален.
    • б) увеличение на желаемую величину.

Я признаю, что я не эксперт по SQL. Я неправильно понимаю, что делает SCOPE_IDENTITY ()? Должен ли я использовать другой подход? Я рассмотрел последовательности в SQL 2012+, но Microsoft говорит, что они не гарантированно будут уникальными по умолчанию.

Эге Эрсоз
источник

Ответы:

25

Это известная и ожидаемая проблема - способ, которым столбцы IDENTITY управляются SQL Server, изменился в SQL Server 2012 ( некоторые предыстории ); по умолчанию он будет кэшировать 1000 значений, и если вы перезапустите SQL Server, перезагрузите сервер, перестанете работать при сбое и т. д., ему придется выбросить эти 1000 значений, потому что у него не будет надежного способа узнать, сколько из них было на самом деле выпущен. Это задокументировано здесь . Существует флаг трассировки, который изменяет это поведение таким образом, что каждое назначение IDENTITY записывается в журнал *, предотвращая эти конкретные пропуски (но не пропуски при откате или удалении); однако важно отметить, что это может быть довольно затратным с точки зрения производительности, поэтому я даже не буду упоминать здесь конкретный флаг трассировки.

* (Лично я думаю, что это техническая проблема, которая может быть решена по-другому, но, поскольку я не пишу движок, я не могу это изменить.)

Чтобы понять, как работают IDENTITY и SEQUENCE:

  • Ни один из них не гарантированно будет уникальным (вам нужно применить это на уровне таблицы, используя первичный ключ или ограничение уникальности)
  • Ни один из них не гарантирует отсутствие пробелов (любой откат или удаление, например, приведет к разрыву, несмотря на эту конкретную проблему)

Уникальность легко обеспечить. Избегать пробелов нет. Вам необходимо определить, насколько важно для вас избежать этих пробелов (теоретически вам вообще не нужно заботиться о пробелах, поскольку значения IDENTITY / SEQUENCE должны быть бессмысленными суррогатными ключами). Если это очень важно, то вам не следует использовать ни одну из реализаций, а вместо этого запустить собственный генератор сериализуемых последовательностей (см. Некоторые идеи здесь , здесь и здесь ) - просто обратите внимание, что это убьет параллелизм.

Много предыстории на эту «проблему»:

Аарон Бертран
источник
Этот ответ (за исключением части «флага трассировки») также применим к большинству других баз данных SQL (которые в любом случае имеют последовательности).
Мустаччо
Спасибо за ответ. Уникальность является единственным наиболее важным требованием. Разрывы не имеют большого значения, если они не большие. например, переход от 1 до 4 будет приемлемым, а от 4 до 1003 - нет.
Эге Эрсоз
1
Краткая версия: значения идентификаторов будут использоваться в качестве номеров заказов на покупку. Клиент запускает ежемесячные отчеты и хочет быстро узнать, сколько заказов было отправлено в этом месяце, просто взглянув на номер заказа. Поэтому мы не можем увеличить его на ~ 1000 (есть еженедельное обслуживание, когда все серверы, включая сервер БД, перезапускаются).
Эге Эрсоз
3
Почему бы вам не дать им очень простой отчет, который просто использует ROW_NUMBER () OVER (PARTITION BY Month ORDER BY ID)? Опять же, идентификационный номер должен быть бессмысленным, это ужасный способ узнать, сколько заказов было принято. Что если в вашем коде есть ошибка, которая удаляет 1000 строк или откатывает 275 транзакций, или 500 заказов законно отменяются?
Аарон Бертран
1
@Ege: «... скажи сколько ... просто посмотрев на номер заказа». Ваши пользователи будут разочарованы. Значения идентичности просто не работают таким образом, и вы (или они) не должны делать никаких подобных предположений. Уникальный? Да. Последовательная? Нет. Правильный способ подсчета заявок, поданных в течение месяца, состоит в том, чтобы ... подсчитывать количество заказов на покупку, поднятых в течение этого месяца, на основе некоторого [неизменяемого] поля Date в каждой записи.
Фил В.
-4

Это проблема SQL Server. Все, что вы можете сделать, это повторно заполнить колонку.

удалить записи с неправильным идентификатором столбца. Переустановите идентичность столбца. И тогда следующая запись имеет правильный идентификатор.

Восстановите идентичность, используя следующую команду sql: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 - последний правильный идентификатор

user190684
источник
1
Что вы имеете в виду под "удалить записи"?
ypercubeᵀᴹ
2
Хм ... кажется, удаление записей может привести к потере данных.
Майкл Грин,