Почему IF (запрос) занимает более часа, когда запрос занимает меньше секунды?

У меня есть следующий SQL:

 IF EXISTS
 (
    SELECT
        1
    FROM
        SomeTable T1
    WHERE
        SomeField = 1
    AND SomeOtherField = 1
    AND NOT EXISTS(SELECT 1 FROM SomeOtherTable T2 WHERE T2.KeyField = T1.KeyField)
)
    RAISERROR ('Blech.', 16, 1)

Таблица SomeTable имеет около 200 000 строк, а таблица SomeOtherTable имеет одинаковую.

Если я выполняю внутренний SQL (SELECT), он выполняется в суб-второй раз, не возвращая строки. Но если я выполняю весь script (IF...RAISERROR), то он занимает более часа. Почему?

Теперь, очевидно, план выполнения отличается - я вижу это в Enterprise Manager, но опять же, почему?

Я мог бы, вероятно, сделать что-то вроде SELECT @num = COUNT(*) WHERE... и затем IF @num > 0 RAISERROR, но... Я думаю, что немного не хватало точки. Вы можете кодировать только ошибку (и это наверняка похоже на ошибку), если вы знаете, что она существует.


ИЗМЕНИТЬ

Я должен упомянуть, что я уже пробовал перетащить запрос в OUTER JOIN в соответствии с ответом @Bohemian, но это не имело никакого значения для времени выполнения.


РЕДАКТИРОВАТЬ 2:

Я приложил план запроса для внутреннего оператора SELECT:

Query Plan - inner SELECT statement

... и план запроса для всего блока IF...RAISERROR:

Query Plan - whole IF statement

Очевидно, что они отображают реальные имена таблиц и полей, но, кроме того, запрос точно такой, как показано выше.

Ответ 1

IF не волшебным образом отключает оптимизацию или повреждает план. Оптимизатор только заметил, что EXISTS нужна только одна строка (например, TOP 1). Это называется "цель строки", и обычно это происходит, когда вы выполняете пейджинг. Но также с EXISTS, IN, NOT IN и такими вещами.

Мое предположение: если вы пишете TOP 1 в исходный запрос, вы получаете тот же (плохой) план.

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

Предлагаю следующие шаги:

  • исправить план, просмотрев индексы и статистику
  • Если это не помогло, измените запрос на IF (SELECT COUNT(*) FROM ...) > 0, который даст исходный план, потому что оптимизатор не имеет цели строки.

Ответ 2

Вероятно, потому, что оптимизатор может выяснить, как превратить ваш запрос в более эффективный запрос, но каким-то образом IF предотвращает это. Только EXPLAIN расскажет вам, почему запрос занимает очень много времени, но я могу рассказать вам, как сделать все это более эффективным... Indtead использует коррелированный подзапрос, который невероятно неэффективен - вы получаете подпрограммы "n", выполняемые для Строки "n" в главной таблице - используйте JOIN.

Попробуйте следующее:

IF EXISTS (
  SELECT 1
  FROM SomeTable T1
  LEFT JOIN SomeOtherTable T2 ON T2.KeyField = T1.KeyField
  WHERE SomeField = 1
  AND SomeOtherField = 1
  AND T2.KeyField IS NULL
) RAISERROR ('Blech.', 16, 1)

"Трюк" здесь состоит в том, чтобы использовать s LEFT JOIN и отфильтровать все объединенные строки, проверив нулевое значение в предложении WHERE, которое выполняется после создания соединения.

Ответ 3

Попробуйте SELECT TOP 1 KeyField. Использование первичного ключа будет работать быстрее в моих предположениях.

ПРИМЕЧАНИЕ. Я разместил это как ответ, поскольку не мог комментировать.