2011-01-21 6 views
2

У меня есть вопрос блокировки SQL Server относительно приложения, которое у нас есть в доме. Приложение принимает данные и сохраняет их в таблице SQL Server. Каждому представлению также присваивается специальный номер каталога (не связанный с полем идентификации в таблице), который является последовательным буквенным числовым номером. Эти цифры вытягиваются из другой таблицы и не генерируются во время выполнения. Таким образом, шагиБлокировка SQL-таблицы

  1. Вставка данных в представлении таблицы
  2. Grab следующий Unassigned Каталог Номер по каталогу Таблица
  3. Назначают Номер Каталога к Представление в таблице Представление

Все эти шаги выполняются последовательно в одной и той же хранимой процедуре.

Его скорость, но иногда нам удается получить две подачи за одну секунду, и им присваивается тот же номер Каталога, который вызывает локализованную версию Апокалипсиса в нашей компании на некоторое время.

Что мы можем сделать, чтобы ограничить переназначение номеров каталогов?

+1

Все три этапа хранимой процедуры заключены в транзакцию? Каков уровень изоляции, установленный для транзакции? – Thomas

+0

Это также апокалипсис, если у вас есть пробелы в присвоенных номерах каталогов? т. е. если T1 присваивается номер каталога, а затем удаляет ошибку, тогда откатывается, это проблема, если этот номер каталога просто пропущен? –

+0

Извините, позвонили и не смогли контролировать мой собственный вопрос. Итак, 1) нет, они сейчас не находятся в транзакции, просто выполняются один за другим в одном и том же sproc. и 2) идеальные арки, но они далеки от апокалиптического –

ответ

4

При получении вашего следующего номера по каталогу, использование строка блокировки для защиты времени между вами найти его, и помечает его как при использовании, например:

set transaction isolation level REPEATABLE READ 
begin transaction 
select top 1 @catalog_number = catalog_number 
    from catalog_numbers with (updlock,rowlock) 
    where assigned = 0 
update catalog_numbers set assigned = 1 where catalog_number = :catalog_number 
commit transaction 
+0

-1. Блокировка строк не имеет ничего общего с транзакциями, она не будет защищать номер каталога вообще. Нет ничего, что удерживало бы две сессии от выбора одного и того же номера каталога, прежде чем один из них его использует. – Guffa

+0

@ Guffa «updlock» предотвращает это. 'UPDATE .. TOP и OUTPUT' могли бы сделать это без необходимости выбора. –

+0

@Martin: Уязвимость блокирует таблицу до конца транзакции. Поскольку нет транзакции, охватывающей оба запроса, у них есть свои собственные транзакции, поэтому транзакция и блокировка заканчиваются до начала второго запроса. – Guffa

1

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

insert into Catalog() values() 
set @CatalogNumber = scope_identity() 

scope_identity функция возвратит идентификатор последней записи, созданной в том же сеансе, поэтому отдельные сессии могут создавать записи в то же время и до сих пор в конечном итоге с правильный идентификатор.

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

+0

+1 Это очевидный разумный способ, если у вас есть роскошь переписать его – Andomar

+0

Не будет ли сеанс блокировки только текущей строки, что позволит создать новую строку? – ispiro

+0

@ispiro: Если вы используете поле идентификации, это не проблема, если другой сеанс создает запись одновременно, так как вы можете получить идентификатор, созданный в сеансе. Если вам нужно сначала определить идентификатор, а затем вставить, вам потребуется транзакция, чтобы вы могли сделать другие сеансы до тех пор, пока вы не выполнили оба действия. – Guffa

0

Мне нравится реакция Аракида. Вы также можете использовать триггер insert в таблице представления, чтобы выполнить это. Триггер будет находиться в области вставки, и вы бы эффективно вставляли логику, чтобы назначить имя_каталога в триггере. Просто хотел поставить здесь свои варианты.

0

Это простое решение. Состояние гонки. Отсутствие блокировки от уровня изоляции ограничительных транзакций. Вероятно, не будет работать в SQL-диалектах, отличных от T-SQL.

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

Этот метод должен работать для вас: просто сделать такое же «заблокированное обновление», который извлекает значение, что-то вроде:

update top 1 CatalogNumber 
set in_use   = 1 , 
    @newCatalogNumber = catalog_number 
from CatalogNumber 
where in_use = 0 

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

drop table dbo.PrimaryKeyGenerator 
go 
create table dbo.PrimaryKeyGenerator 
(
    id   varchar(100) not null , 
    current_value int   not null default(1) , 

    constraint PrimaryKeyGenerator_PK primary key clustered (id) , 

) 
go 
drop procedure dbo.GetNewPrimaryKey 
go 
create procedure dbo.GetNewPrimaryKey 

    @name varchar(100) 

as 

    set nocount     on 
    set ansi_nulls    on 
    set concat_null_yields_null on 
    set xact_abort    on 

    declare 
    @uniqueValue int 

    -- 
    -- put the supplied key in canonical form 
    -- 
    set @name = ltrim(rtrim(lower(@name))) 

    -- 
    -- if the name isn't already defined in the table, define it. 
    -- 
    insert dbo.PrimaryKeyGenerator (id) 
    select id = @name 
    where not exists (select * 
        from dbo.PrimaryKeyGenerator pkg 
        where pkg.id = @name 
        ) 

    -- 
    -- now, an interlocked update to get the current value and increment the table 
    -- 
    update PrimaryKeyGenerator 
    set @uniqueValue = current_value , 
     current_value = current_value + 1 
    where id = @name 

    -- 
    -- return the new unique value to the caller 
    -- 
    return @uniqueValue 
go 

Чтобы использовать его:

объявить @pk Int Exec @pk = dbo.GetNewPrimaryKey 'Foobar' выберите @pk

Trivial моднику его возвращать результирующий набор или вернуть значение через параметр OUTPUT.

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