2009-10-08 1 views
1

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

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

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

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

 
SELECT @Counter = counter, @ShareId=share_id, @Id=id 
FROM Number_Setup 
WHERE [email protected] 
AND Counter_Type='I' 

IF isnull(@ShareId,0) > 0 
BEGIN 
    -- use parent counter 
    SELECT @Counter = counter, @ID=id 
    FROM Number_Setup 
    WHERE [email protected] 
END 

SELECT @NewCounter = @Counter + 1 

UPDATE Number_Setup SET Counter = @NewCounter 
WHERE [email protected] 

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

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

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

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

Как я могу обеспечить отсутствие дубликатов?

+0

какая версия сервера Sql? – RBarryYoung

+0

на данный момент, 2000. –

ответ

4

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

UPDATE Number_Setup SET Counter = Counter+1 
OUTPUT INSERTED.Counter 
WHERE [email protected]; 

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

declare @NewCounter int; 
declare @tabCounter table (NewCounter int); 
UPDATE Number_Setup SET Counter = Counter+1 
OUTPUT INSERTED.Counter INTO @tabCounter (NewCounter) 
WHERE [email protected] 
SELECT @NewCounter = NewCounter FROM @tabCounter; 

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

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

+0

благодарит за отзыв. схема вне нашего контроля, и мой пример составил имена полей и не является прямой копией фактического sproc :) –

+0

Очень полезно, спасибо! –

0

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

0

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

+1

scope_identity() будет лучше, чем @@, чтобы убедиться, что вы вернули личность, которую вы только что создали. – adrianbanks

0

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

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

+0

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

Смежные вопросы