Я хочу сделать SELECT / INSERT версия UPSERT. Ниже приведен шаблон существующего кода:
// CREATE TABLE Table (RowID INT NOT NULL IDENTITY(1,1), RowValue VARCHAR(50))
IF NOT EXISTS (SELECT * FROM Table WHERE RowValue = @VALUE)
BEGIN
INSERT Table VALUES (@Value)
SELECT @id = SCOPEIDENTITY()
END
ELSE
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE)
Запрос будет вызываться из множества одновременных сеансов. Мои тесты производительности показывают, что он последовательно генерирует нарушения первичного ключа при определенной нагрузке.
Существует ли метод с высокой степенью параллелизма для этого запроса, который позволит ему поддерживать производительность, в то же время избегая вставки данных, которые уже существуют?
Вы можете использовать БЛОКИРОВКИ, чтобы сделать вещи СЕРИАЛИЗУЕМЫМИ, но это снижает параллелизм. Почему бы сначала не попробовать обычное условие («в основном вставить или в основном выбрать»), а затем безопасно обработать «корректирующее» действие? То есть шаблон "JFDI"...
В основном ожидается INSERT (примерно 70-80%+):
Просто попробуйте вставить. Если это не удается, строка уже создана. Не нужно беспокоиться о параллелизме, потому что TRY/CATCH обрабатывает дубликаты за вас.
BEGIN TRY
INSERT Table VALUES (@Value)
SELECT @id = SCOPE_IDENTITY()
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
ELSE -- only error was a dupe insert so must already have a row to select
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
В основном SELECT:
Похоже, но сначала попробуйте получить данные. Нет данных = требуется ВСТАВИТЬ. Опять же, если 2 одновременных вызова пытаются выполнить INSERT, потому что они оба обнаружили, что в строке отсутствуют дескрипторы TRY/CATCH.
BEGIN TRY
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
IF @@ROWCOUNT = 0
BEGIN
INSERT Table VALUES (@Value)
SELECT @id = SCOPE_IDENTITY()
END
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
ELSE
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
Второй, кажется, повторяется, но он очень совпадающий. Блокировки добились бы того же, но за счет параллелизма...
Изменить:
Почему не использовать MERGE...
Если вы используете предложение OUTPUT, оно вернет только то, что обновлено. Поэтому вам нужен фиктивный UPDATE для создания таблицы INSERTED для предложения OUTPUT. Если вам нужно делать фиктивные обновления с большим количеством вызовов (как подразумевается OP), это много записей журнала просто , чтобы иметь возможность использовать MERGE.