Обгортання запиту, якщо існує, це робить дуже повільним


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)

Наведений вище запит виконується за три секунди.

Якщо вищезазначений запит повертає будь-яке значення, ми хочемо, щоб збережена процедура EXIT, тому я переписав її як нижче:

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 Paste

Примітка. Обидва запити однакові (з використанням параметрів), різниця полягає лише в тому, що EXISTS(можливо, я помилився, хоч анонімізував).

Сценарії створення таблиці наведені нижче:

http://pastebin.com/CgSHeqXc - маленька статистика таблиці
http://pastebin.com/GUu9KfpS - велика статистика таблиці


Відповіді:


18

Як було пояснено Пол Уайт в своєму блозі: Всередині оптимізатор: Роу мети в Глибини в EXISTSВносить ряд мета, яка вважає за краще NESTED LOOPSабо MERGE JOINбільшHASH MATCH

В якості останнього прикладу розглянемо, що логічне напівз'єднання (наприклад, підзапит, запроваджений із EXISTS) поділяє загальну тему: його слід оптимізувати для швидкого пошуку першого відповідного рядка.

У вашому запиті, очевидно, трапляється ввести вкладені петлі та видалити паралелізм, що призводить до більш повільного плану.

Тож вам, мабуть, доведеться знайти спосіб переписати запит, не використовуючи NOT EXISTSзапит.

Ви можете піти з переписування запиту за допомогою LEFT OUTER JOINі перевірити, чи не було рядка в малій таблиці, перевіривши на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.

Пов’язані запитання та відповіді: ЯКЩО ВІДПОВІСТЬ займе більше часу, ніж вбудований оператор select


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 Матч. Не вивчали, чому саме так відбувається, було б цікаво дізнатися чому.

Сподіваюся, що це допомагає!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.