Вставьте, если не существует, одновременно

13

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

select @_id = Id from table1 where othervalue = @_othervalue
IF( @_id IS NULL)
BEGIN
    insert into table1 (othervalue) values (@_othervalue)
    select @_id = Id from table1 where othervalue = @_othervalue
END

Когда мы запускаем 3 или 4 из этих сохраненных процедур одновременно, мы иногда получаем несколько вставок.

Я планирую исправить это так:

insert into table1 (othervalue) 
    select TOP(1) @_othervalue as othervalue from table1 WITH(UPDLOCK) 
    where NOT EXISTS ( select * from table1 where othervalue = @_othervalue )

select @_id = Id from table1 where othervalue = @_othervalue

Вопрос в том, как одновременно вставлять без дубликатов в SQL Server? Меня беспокоит тот факт, что я должен использовать ТОП, чтобы вставить только один раз.

Крис
источник
1
Вам не нужно использовать TOP. Удалите ссылку на таблицу FROM из оператора SELECT.
ErikE
@Gerg Я думаю, ты прав.
Крис

Ответы:

7

Вы можете использовать оператор слияния с serializableподсказкой.

merge table1 with (serializable) as T 
using (select @_othervalue as othervalue) as S
on T.othervalue = S.othervalue
when not matched then
  insert (othervalue) values (othervalue);
Микаэль Эрикссон
источник
Вы стресс-тестировали свой подход из двух или более соединений?
AK
2
@AlexKuznetsov - Я сделал это некоторое время назад для другого вопроса о SO. Я использовал две вкладки в SSMS. Сначала протестировал insert ... where not exist ...шаблон и обнаружил, что вы можете получить взаимоблокировки и нарушения ключа, поэтому там было необходимо использовать Updlock и сериализуемые. Затем я протестировал оператор слияния и подумал, что он будет обрабатывать вещи немного лучше, и он сделал это потому, что там, где нет тупиков, но мне все еще приходилось использовать сериализуемый код, чтобы не было нарушений ключа.
Микаэль Эрикссон
1
Это действительно потрясающий ответ.
Крис Марисик
5

Если вам не нужны дубликаты в столбце «othervalue», вы можете сделать это, создав в unique constraintэтом столбце. Запрос будет:

 ALTER TABLE table1
 ADD CONSTRAINT unique_c_othervalue UNIQUE(othervalue)

Это выдаст ошибку, если запрос попытается вставить повторяющееся значение в столбец «другое значение».

StanleyJohns
источник
Как это будет работать, если уникальным ограничением является кортеж из двух строк?
Крис
1
@Chris Как у вас есть уникальное ограничение, которое охватывает строки?
Аарон Бертран
@ Аарон У меня, наверное, нет терминологии, но у нас есть две строки, которые должны быть уникальными. Я не думаю, что это применяется в нашей схеме.
Крис
2

Используйте уникальное ограничение, которое рекомендует @StanleyJohns. Затем используйте BEGIN TRY END TRY вокруг вашего оператора вставки.

select @_id = Id from table1 where othervalue = @_othervalue
IF( @_id IS NULL)
BEGIN
    BEGIN TRY
        insert into table1 (othervalue) values (@_othervalue)
        select @_id = Id from table1 where othervalue = @_othervalue        
    END TRY
    BEGIN CATCH
        select @_id = Id from table1 where othervalue = @_othervalue        
    END CATCH
END
mrdenny
источник