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


35

Коли я запускаю наступний код, це займає 22,5 хвилин і робить 106 мільйонів читання. Однак, якщо я запускаю лише внутрішній оператор select, він займає лише 15 секунд і читує 264k. Як бічна примітка, запит вибору не повертає записів.

Будь-яка ідея, чому IF EXISTSб змусити його працювати так довше і зробити так багато читає? Я також змінив заяву select для виконання, SELECT TOP 1 [dlc].[id]і я вбив її через 2 хвилини.

Як тимчасове виправлення я змінив його, щоб зробити підрахунок (*) і призначити це значення змінній @cnt. Потім він робить IF 0 <> @cntзаяву. Але я подумав, що EXISTSбуло б краще, тому що якщо в операторі select повернуті записи, він перестане виконувати пошук / пошук, як тільки знайде хоча б один запис, тоді як 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 для виконання, SELECT TOP 1 [dlc].[id]і я вбив її через 2 хвилини.

Як я пояснив у своїй відповіді на це пов'язане питання:

Як (і чому) ТОП впливає на план виконання?

Використання EXISTSвводить ціль рядка, де оптимізатор виробляє план виконання, спрямований на швидке розміщення першого рядка. При цьому передбачається, що дані розподіляються рівномірно. Наприклад, якщо статистика показує, що 100 очікуваних матчів у 100 000 рядків, буде припускатися, що для першого матчу доведеться прочитати лише 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;

Алт-приклад, який ви надали, пробіг за 3,75 хвилин і провів 46 м читання. Отже, хоч швидше, ніж мій початковий запит, я думаю, що в цьому випадку я буду дотримуватися @cnt = count (*) та оцінювати змінну згодом. Тим більше, що 99% часу, коли це проходить, у ньому нічого не буде. Це звучить як виходячи з ваших відповідей і Роба, що існує, якщо це справді добре, лише якщо ви дійсно очікуєте на якийсь результат і такий результат рівномірно розподіляється у ваших даних.
Кріс Вудс

3
@ChrisWoods: Ви сказали: "Тим більше що 99% часу, коли це проходить, у ньому нічого не буде". Це в значній мірі гарантує, що ціль рядка - це погана ідея, оскільки ви очікуєте, що там зазвичай НЕ буде рядків, і вам доведеться просканувати все, щоб виявити, що таких немає. Якщо ви не можете додати якийсь розумний індекс, дотримуйтесь COUNT (*).
Росс Прессер

25

Оскільки EXISTS повинен знайти лише один рядок, він використовуватиме ціль рядка - один. Це може створити план, який не є ідеальним. Якщо ви очікуєте, що це буде для вас таким чином, заповніть змінну з результатом a, COUNT(*)а потім протестуйте цю змінну, щоб побачити, чи є вона більше 0.

Отже ... Завдяки малій цілі рядка це дозволить уникнути блокуючих операцій, таких як побудова хеш-таблиць або сортування потоків, які можуть бути корисні для об'єднання об'єднань, оскільки буде зрозуміло, що він повинен знайти щось досить швидко, і тому вкладені петлі найкраще, якщо б щось знайшов. За винятком того, що це може зробити план, який набагато гірший у всьому наборі. Якщо пошук одного ряду був швидким, ви хочете, щоб цей спосіб уникнув блокування ...

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