Выберите / вставьте версию Upsert: есть ли шаблон проектирования для высокого параллелизма? - PullRequest
11 голосов
/ 29 августа 2010

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

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

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

Ответы [ 3 ]

15 голосов
/ 29 августа 2010

Вы можете использовать LOCKS, чтобы сделать вещи SERIALIZABLE, но это уменьшает параллелизм. Почему бы не попробовать сначала общее условие («в основном вставить или в основном выбрать»), а затем выполнить безопасную обработку «корректирующего» действия? То есть шаблон "JFDI" ...

Ожидаются в основном INSERT (парк мячей 70-80% +):

Просто попробуйте вставить. Если это не удается, строка уже создана. Не нужно беспокоиться о параллелизме, потому что TRY / CATCH имеет дело с дубликатами для вас.

BEGIN TRY
   INSERT Table VALUES (@Value)
   SELECT @id = SCOPEIDENTITY()
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

В основном ВЫБИРАЕТ:

Аналогично, но сначала попробуйте получить данные. Нет данных = ВСТАВКА нужна. Опять же, если 2 одновременных вызова пытаются ВСТАВИТЬ, потому что они обнаружили, что в строке отсутствуют маркеры TRY / CATCH.

BEGIN TRY
   SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
   IF @@ROWCOUNT = 0
   BEGIN
       INSERT Table VALUES (@Value)
       SELECT @id = SCOPEIDENTITY()
   END
END TRY
BEGIN CATCH
    IF ERROR_NUMBER() <> 2627
      RAISERROR etc
    ELSE
      SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH

2-й, кажется, повторяется, но он очень параллельный. Замки достигли бы того же, но за счет параллелизма ...

Edit:

Почему не для использования MERGE ...

Если вы используете предложение OUTPUT, оно вернет только то, что обновлено. Таким образом, вам нужно фиктивное ОБНОВЛЕНИЕ для генерации таблицы INSERTED для предложения OUTPUT. Если вам приходится делать фиктивные обновления со многими вызовами (как подразумевается в OP), это означает, что в журнал заносится всего , чтобы можно было использовать MERGE.

1 голос
/ 30 декабря 2010
// CREATE TABLE Table (RowID INT NOT NULL IDENTITY(1,1), RowValue VARCHAR(50))

- убедитесь, что у вас есть некластеризованный уникальный индекс для RowValue и RowID в качестве кластерного индекса.

IF EXISTS (SELECT * FROM Table WHERE RowValue = @VALUE)
   SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
ELSE BEGIN
   INSERT Table VALUES (@Value)
   SELECT @id = SCOPEIDENTITY()
END
0 голосов
/ 14 июня 2017

Как всегда, ответ ГБН верен и в конечном итоге приведет меня туда, где я должен был быть.Тем не менее, я обнаружил конкретный крайний случай, который не был охвачен его подходом.Это ошибка 2601, которая идентифицирует Unique Index Violation.

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

...
declare @errornumber int = ERROR_NUMBER()
if @errornumber <> 2627 and @errornumber <> 2601
...

Надеюсь, это кому-нибудь поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...