IF EXISTS занимает больше времени, чем встроенный оператор select

35

Когда я запускаю следующий код, это занимает 22,5 минуты и читает 106 миллионов раз. Тем не менее, если я запускаю только внутреннюю инструкцию выбора, она занимает всего 15 секунд и читает 264 Кб. Как примечание, запрос на выборку не возвращает никаких записей.

Любая идея, почему это IF EXISTSзаставило бы это бежать намного дольше и делать так много чтений? Я также изменил оператор выбора, чтобы сделать, SELECT TOP 1 [dlc].[id]и я убил его через 2 минуты.

В качестве временного исправления я изменил его на счетчик (*) и присвоил это значение переменной @cnt. Тогда это делает IF 0 <> @cntзаявление. Но я подумал, EXISTSчто будет лучше, потому что, если в операторе выбора будут возвращены записи, он прекратит сканирование / поиск, когда найдет хотя бы одну запись, тогда как count(*)запрос завершит полный запрос. Что мне не хватает?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END
Крис Вудс
источник
4
Чтобы избежать проблемы цели строки, другой идеей (непроверенной, обратите внимание!) Может быть попытка обратного - IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Аарон Бертран

Ответы:

32

Любая идея, почему IF EXISTS заставило бы это бежать намного дольше и делать так много чтений? Я также изменил оператор выбора, чтобы сделать, SELECT TOP 1 [dlc].[id]и я убил его через 2 минуты.

Как я объяснил в своем ответе на этот связанный вопрос:

Как (и почему) TOP влияет на план выполнения?

С помощью EXISTS вводит цель строки, где оптимизатор создает план выполнения, нацеленный на быстрое нахождение первой строки. При этом предполагается, что данные распределены равномерно. Например, если статистика показывает, что в 100 000 строк есть 100 ожидаемых совпадений, она будет предполагать, что для поиска первого совпадения потребуется прочитать только 1000 строк.

Это приведет к увеличению времени выполнения, если это предположение окажется ошибочным. Например, если SQL Server выбирает метод доступа (например, неупорядоченное сканирование), который обнаруживает первое совпадающее значение очень поздно при поиске, это может привести к почти полному сканированию. С другой стороны, если в первых нескольких строках будет найдена совпадающая строка, производительность будет очень хорошей. Это фундаментальный риск для ряда целей - непоследовательная производительность.

В качестве временного исправления я изменил его на счетчик (*) и присвоил это значение переменной

Обычно можно переформулировать запрос так, чтобы цель строки не была назначена. Без цели строки запрос все еще может быть завершен при обнаружении первой подходящей строки (если она написана правильно), но стратегия плана выполнения, вероятно, будет другой (и, надеюсь, более эффективной). Очевидно, что count (*) потребует чтения всех строк, поэтому это не идеальная альтернатива.

Если вы работаете с SQL Server 2008 R2 или более поздней версии , вы также можете использовать документированный и поддерживаемый флаг трассировки 4138, чтобы получить план выполнения без цели строки. Этот флаг также может быть указан с помощью поддерживаемой подсказки OPTION (QUERYTRACEON 4138) , хотя имейте в виду , что для этого требуется разрешение системного администратора во время выполнения , если только оно не используется с руководством по планированию.

к несчастью

Ничто из вышеперечисленного не является функциональным с IF EXISTSусловным утверждением. Это относится только к обычному DML. Это будет работать с альтернативной SELECT TOP (1)формулировкой, которую вы пробовали. Это может быть лучше, чем использоватьCOUNT(*) , которое должно подсчитывать все подходящие строки, как упоминалось ранее.

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

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;
Пол Уайт говорит, что GoFundMonica
источник
Другой пример, который вы предоставили, запустился за 3,75 минуты и произвел 46 мес. Поэтому, хотя и быстрее, чем мой исходный запрос, я думаю, что в этом случае я буду придерживаться @cnt = count (*) и оценивать переменную впоследствии. Тем более, что в 99% случаев это будет ничего не значить. Судя по вашим ответам и ответам Роба, «Существовать» хорошо только в том случае, если вы действительно ожидаете какого-то результата и этот результат равномерно распределен в ваших данных.
Крис Вудс
3
@ChrisWoods: Вы сказали: «Тем более, что в 99% случаев это не будет ничего». Это в значительной степени гарантирует, что цель строки одного - плохая идея, так как вы ожидаете, что строк обычно НЕТ, и вам придется сканировать все, чтобы найти, что их нет. Если вы не можете добавить какой-нибудь умный указатель, используйте COUNT (*).
Росс Прессер
25

Поскольку EXISTS нужно только найти одну строку, он будет использовать цель строки, равную единице. Иногда это может привести к не совсем идеальному плану. Если вы ожидаете, что это будет так для вас, заполните переменную с результатомCOUNT(*) а затем протестируйте эту переменную, чтобы увидеть, больше ли это 0.

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

Роб Фарли
источник