Обеспечение уникальных номеров из базы данных sql server

StackOverflow https://stackoverflow.com/questions/1540917

Вопрос

У меня есть приложение, которое использует номера инцидентов (среди других типов номеров).Эти числа хранятся в таблице под названием "Number_Setup", которая содержит текущее значение счетчика.

Когда приложение генерирует новый инцидент, оно number_setupтаблица и получает требуемую строку счетчика номеров (счетчики могут сбрасываться ежедневно, еженедельно и т.д. И сохраняются как int).Затем он увеличивает счетчик и обновляет строку новым значением.

Приложение является многопользовательским (примерно 100 пользователей одновременно, а также задания sql, которые запускают и собирают 100 записей инцидентов и запрашивают номера инцидентов для каждого).В таблице инцидентов есть несколько повторяющихся номеров инцидентов, где они не должны дублироваться.

Сохраненная процедура используется для извлечения следующего счетчика.

SELECT @Counter = counter, @ShareId=share_id, @Id=id
FROM Number_Setup
WHERE LinkTo_ID=@LinkToId
AND Counter_Type='I'

IF isnull(@ShareId,0) > 0
BEGIN 
    -- use parent counter
    SELECT @Counter = counter, @ID=id
    FROM Number_Setup
    WHERE Id=@ShareID
END

SELECT @NewCounter = @Counter + 1

UPDATE Number_Setup SET Counter = @NewCounter
WHERE id=@Id

Теперь я окружил этот блок транзакцией, но я не совсем уверен, что это на 100% решит проблему, так как я думаю, что все еще существуют общие блокировки, поэтому счетчик можно прочитать в любом случае.

Возможно, я смогу проверить, что счетчик не был обновлен, в инструкции update

UPDATE Number_Setup SET Counter = @NewCounter
WHERE Counter = @Counter
IF @@ERROR = 0 AND @@ROWCOUNT > 0 
    COMMIT TRANSACTION
ELSE
    ROLLBACK TRANSACTION

Я уверен, что это распространенная проблема с номерами счетов-фактур в финансовых приложениях и т.д.
Я также не могу поместить логику в код и использовать блокировку на этом уровне.Я также заблокирован в режиме ОЖИДАНИЯ, но я не уверен в его применимости.Должно ли это быть указано в двух операторах SELECT?

Как я могу гарантировать, что дубликаты не будут созданы?

Это было полезно?

Решение

Хитрость заключается в том, чтобы выполнить обновление счетчика и чтение за одну атомарную операцию:

UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter 
WHERE id=@Id;

Это, однако, не присваивает новый счетчик @NewCounter, а вместо этого возвращает его клиенту в качестве результирующего набора.Если вам нужно назначить его, используйте промежуточную табличную переменную для вывода нового счетчика В:

declare @NewCounter int;
declare @tabCounter table (NewCounter int);
UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter INTO @tabCounter (NewCounter)
WHERE id=@Id
SELECT @NewCounter = NewCounter FROM @tabCounter;

Это решает проблему атомарного увеличения счетчика.У вас все еще есть другие условия гонки в вашей процедуре, потому что LinkTo_Id и share_id все еще могут быть обновлены после первого выбора, так что вы можете увеличить счетчик неправильного элемента ссылки, но это не может быть решено только из этого примера кода, поскольку это зависит также от кода, который фактически обновляет shared_id и / или LinkTo_Id .

Кстати, вам следует привыкнуть называть свои поля согласованным регистром.Если они являются названный последовательно, тогда вы должен используйте точный регистр соответствия в коде T-SQL.Ваши скрипты теперь работают нормально только потому, что у вас сервер сортировки без учета регистра, если вы выполняете развертывание на сервере сортировки с учетом регистра и ваши скрипты не соответствуют точному регистру имен полей / таблиц, ошибок будет множество.

Другие советы

вы пробовали использовать GUID вместо автоинкрементов в качестве вашего уникального идентификатора?

Если у вас есть возможность изменить свое задание, которое получает несколько записей, я бы изменил подход, чтобы ваш счетчик был столбцом идентификаторов.Затем, когда вы получите следующую запись, вы можете просто выполнить вставку и получить идентификатор @@ таблицы.Это гарантировало бы, что вы получите наибольшее число.Вам также пришлось бы выполнить dbccReseed для сброса счетчика вместо того, чтобы просто обновлять таблицу, когда вы хотите сбросить идентификатор.Единственная проблема заключается в том, что вам придется выполнить около 100 вставок как часть вашего задания sql, чтобы получить группу идентификаторов.Это может потребовать слишком больших накладных расходов, но использование столбца identity является гарантированным способом получения уникальных номеров.

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

вместо чтения и обновления из столбца 'Counter' в таблице Number_Setup, почему бы вам просто не использовать первичный ключ автоматического включения для вашего счетчика?У вас никогда не будет повторяющегося значения для первичного ключа.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top