Выбор / вставка версии Upsert: есть ли шаблон проектирования для высокого параллелизма?

Я хочу сделать 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)

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

Существует ли метод с высокой степенью параллелизма для этого запроса, который позволит ему поддерживать производительность, в то же время избегая вставки данных, которые уже существуют?

11
задан 8kb 29 August 2010 в 07:34
поделиться

1 ответ

Вы можете использовать БЛОКИРОВКИ, чтобы сделать вещи СЕРИАЛИЗУЕМЫМИ, но это снижает параллелизм. Почему бы сначала не попробовать обычное условие («в основном вставить или в основном выбрать»), а затем безопасно обработать «корректирующее» действие? То есть шаблон "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.

15
ответ дан 3 December 2019 в 07:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: