Что быстрее, существует до или после вставки? - PullRequest
8 голосов
/ 17 февраля 2010

У меня есть SP в SQL Server, который запускается сотни раз в минуту и ​​должен проверять входящий трафик в базе данных. На данный момент он делает следующее

INSERT INTO table
SELECT @value1,@value2 WHERE NOT EXISTS 
(SELECT * FROM table WHERE value1 = @value1 AND value2 = @value2);

Тем не менее, я мог бы также пойти с

IF NOT EXISTS(SELECT * FROM table WHERE value1 = @value1 AND value2 = @value2)    
   INSERT INTO table (value1,value2) VALUES (@value1,@value2);

Что будет быстрее? Я чувствую, что между ними нет большой разницы, но я исторически не очень хорош в TSQL ... = /

ОБНОВЛЕНИЕ: К сожалению ... означало, что EXISTS использует более 1 значения, чтобы найти, существует ли запись, поэтому уникальное ограничение не будет работать. Отредактировал образец, чтобы отразить это ...

Ответы [ 6 ]

5 голосов
/ 17 февраля 2010

Оба варианта неверны. Вы вставите пары дубликатов @ value1, @ value2, гарантировано .

Правильный способ справиться с этим - применить уникальное ограничение для двух столбцов и всегда вставлять и обрабатывать нарушение ограничения:

ALTER TABLE Table ADD CONSTRAINT uniqueValue1Value UNIQUE (value1, values2);

и вставить:

BEGIN TRY
   INSERT INTO Table (value1, value2) VALUES (@value1, @value2);
END TRY
BEGIN CATCH
   DECLARE @error_number int, @error_message NVARCHAR(4000), @xact_state INT;
   SET @error_number = ERROR_NUMBER();
   SET @error_message = ERROR_MESSAGE();
   SET @xact_state = XACT_STATE();
   IF (@xact_state = -1)
   BEGIN
     ROLLBACK TRANSACTION;
   END
   IF (@error_number != 2627) /* 2627 is ' Cannot insert duplicate key in object ...' */
   BEGIN
      RAISERROR(N'Error inserting into Table: %i %s', 16,1, @errror_number, @error_message);
   END
ENd CATCH

Хотя это может показаться сложным, нужно рассмотреть небольшую деталь под названием правильность . Это намного проще по сравнению с решением, основанным на подсказках блокировки. Это также самое эффективное решение: только один поиск. Всем остальным решениям нужно как минимум два поиска (один для подтверждения того, что он может быть вставлен, другой для вставки).

3 голосов
/ 17 февраля 2010

В трудно параллельной среде одновременное INSERT может произойти между IF NOT EXISTS и INSERT во втором запросе.

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

Однако вы не должны полагаться исключительно на это поведение. Поместите дополнительное ограничение UNIQUE на value.

Это не только сделает базу данных более согласованной, но и создаст индекс, который сделает первый запрос более быстрым.

1 голос
/ 17 февраля 2010

После добавления gazillion комментариев к этому вопросу и его ответов у меня будет свой собственный ответ на него.

Я не ожидал бы какой-либо существенной разницы в производительности между двумя предложенными, предложенными в первоначальном вопросе. С одной стороны, как указал Рэй, второй подход может избавить вас от некоторой подготовки к вставке, но с другой стороны, СУБД обычно лучше всего работает с пакетными операторами, как в первом решении.

KM и DVK предлагают добавить ограничение UNIQUE, которое сделает тест уникальности неявным, но потребует от вас добавить некоторую обработку ошибок вокруг вашего оператора INSERT. Мне трудно определить, почему это должно добавить дополнительную производительность, , при условии, что у вас уже есть индекс, охватывающий два столбца . Если у вас нет такого индекса, добавьте его и пересмотрите свою потребность в большей производительности.

Независимо от того, выполняется ли проверка уникальности явно или неявно, AFAIK не имеет значения. Если что-либо получено путем проверки, выполненной «внутри» желудка СУБД, это усиление может быть просто съедено накладными расходами, связанными с ошибками создания и обработки при наличии дубликатов.


Итог: предполагая, что индекс уже создан, если вам все еще не хватает производительности, я рекомендую вам выполнить эмпирические тесты трех предложенных решений. Создайте небольшую программу, которая имитирует ожидаемые входные данные, и унесите каждое из трех решений несколькими миллиардами строк, включая вероятное количество дубликатов. Сделайте это, обязательно опубликуйте свои результаты: -)

1 голос
/ 17 февраля 2010

просто сделайте это и проигнорируйте любую ошибку (предполагается уникальное ограничение на значение) ...

BEGIN TRY
    INSERT INTO Table (value) VALUES (@value);
END TRY
BEGIN CATCH
    PRINT 'it was already in there!'
END CATCH

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

(SELECT * FROM Table WITH (UPDLOCK, HOLDLOCK)  WHERE value = @value);

однако, моя предложенная идея просто ВСТАВИТЬ и игнорировать любую дублирующую ошибку ограничения также позволит избежать условия гонки.

1 голос
/ 17 февраля 2010

Если вы хотите, чтобы значения были уникальными, почему бы просто не создать уникальное ограничение для значения, сделать INSERT без SELECT и корректно обработать ошибку нарушения ограничения?

Это было бы быстрее, чем любой из этих подходов.

Кроме того, ваш первый подход не работает - к тому времени, когда вы выбираете, вы уже вставили значение, так что select, очевидно, найдет то, что вы только что вставили.

0 голосов
/ 17 февраля 2010

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

...