Заключение запроса в IF EXISTS делает его очень медленным

16

У меня есть запрос ниже:

select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)

Вышеуказанный запрос завершается за три секунды.

Если приведенный выше запрос возвращает какое-либо значение, мы хотим, чтобы хранимая процедура завершила работу, поэтому я переписал его, как показано ниже:

If Exists(
select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End

Однако это занимает 10 минут.

Я могу переписать вышеуказанный запрос, как показано ниже, который также выполняется менее чем за 3 секунды:

  select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End

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

Вышеприведенное возвращает пустой набор результатов, поэтому, если я пойду с этим подходом, мне придется изменить свой C # и снова выполнить развертывание.

Итак, мой вопрос,

почему использование просто IF EXISTSменяет план так долго?

Ниже приведены подробности, которые могут вам помочь, и дайте мне знать, если вам нужны какие-либо подробности:

  1. Создайте скрипт таблицы и статистики, чтобы получить тот же план, что и у меня
  2. План медленного исполнения
  3. План быстрого исполнения

    Медленный план с помощью Brentozar Вставить план
    Быстрый план с помощью Brentozar Вставить план

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

Скрипты создания таблицы приведены ниже:

http://pastebin.com/CgSHeqXc - статистика малых таблиц
http://pastebin.com/GUu9KfpS - статистика больших таблиц

TheGameiswar
источник
Обсуждение по этому вопросу было перенесено в этот чат .
Пол Уайт восстановил Монику

Ответы:

18

Как было объяснено Пол Уайт в своем блоге: Внутри Оптимизатор: Роу цели в Глубины в EXISTSВносит ряд цель, которая предпочитает NESTED LOOPSили MERGE JOINболееHASH MATCH

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

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

Таким образом, вам, вероятно, потребуется найти способ переписать ваш запрос без использования NOT EXISTSзапроса.

Вы можете сойти с рук, переписав запрос с помощью a LEFT OUTER JOINи проверив, что в smalltable нет строки, протестировавNULL

If EXISTS(
    SELECT databasename
    FROM somedb.dbo.bigtable l
    LEFT JOIN dbo.smalltable c ON c.source = l.source
    WHERE databasename = 'someval'
    AND source <> 'kt'
    AND c.source IS NULL
)

Вы также можете использовать EXCEPTзапрос, в зависимости от того, сколько полей вам нужно сравнить, например:

If EXISTS(
   SELECT source
   FROM somedb.dbo.bigtable l
   WHERE databasename = 'someval'
   AND source <> 'kt'

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)

Напомним, у Аарона Бертранда есть запись в блоге, в которой приводятся причины, по которым он предпочитает «НЕ СУЩЕСТВУЕТ», которую вы должны прочитать, чтобы увидеть, работают ли другие подходы лучше, и знать о потенциальных проблемах правильности в случае значений NULL.

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

Том V - Команда Моника
источник
0

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

If not exists(
    select databasename 
    from somedb.dbo.bigtable l
    inner hash join dbo.smalltable c 
        on c.source = l.source
where databasename ='someval' and source  <>'kt')
begin
    Raiserror('Source missing',16,1)
    Return
end

При использовании EXISTS или NOT EXISTS план запроса, сгенерированный SQL Server, с операцией NESTED LOOP, предполагая, что он должен пройти по всем строкам в наборе одна за другой, ища первую строку, чтобы удовлетворить условию. Использование HASH JOIN ускорит его.

Артем Мачнев
источник
Чем вы, протестируете
TheGameiswar
0

Я столкнулся с той же проблемой, мне удалось обойти себя, избегая использования «EXISTS» и используя функцию «COUNT ()» и оператор «IF ... ELSE».

Для вашего примера попробуйте следующее:

IF
(
    SELECT
        COUNT(l.databasename) + 1 AS databasename
    FROM somedb.dbo.bigtable AS l

    WHERE   l.databasename ='someval'
        AND l.[source]  <> 'kt'
        AND NOT EXISTS(SELECT 1 FROM dbo.smalltable AS c WHERE c.[source]=l.[source])
) > 1 --Acts like EXISTS
BEGIN
    RAISERROR('Source missing', 16, 1)
RETURN
END

Причина, по которой я добавляю «+ 1» к счетчику, заключается в том, что я могу использовать «> 1» в условии IF, использование «> 0» или «<> 0» заставит запрос использовать вложенные циклы вместо HASH Совпадение. Не задумывался, почему это происходит, было бы интересно узнать почему.

Надеюсь, это поможет!

Хейдер Нахи
источник