T-SQL-запит із використанням абсолютно іншого плану в залежності від кількості рядків, які я оновлюю


20

У мене є оператор SQL UPDATE з пунктом "TOP (X)", і рядок, в якому я оновлюю значення, містить близько 4 мільярдів рядків. Коли я використовую "TOP (10)", я отримую один план виконання, який виконується майже миттєво, але коли я використовую "TOP (50)" або більше, запит ніколи (принаймні, не під час очікування) закінчується, і він використовує зовсім інший план виконання. Менший запит використовує дуже простий план з парою шукає індекс і вкладений вкладений цикл, де для того самого запиту (з різною кількістю рядків у пункті TOP оператора UPDATE) використовується план, який включає дві різні пошуки індексу , котушка столу, паралелізм і купа інших складностей.

Я використав "OPTION (USE PLAN ...)", щоб змусити його використовувати план виконання, сформований меншим запитом - коли я це роблю, я можу оновити цілих 100 000 рядків за кілька секунд. Я знаю, що план запитів хороший, але SQL Server вибере цей план самостійно лише тоді, коли буде задіяна лише невелика кількість рядків - будь-яка пристойно велика кількість рядків у моєму оновленні призведе до неоптимального плану.

Я подумав, що в цьому може бути винен паралелізм, тому я став MAXDOP 1на запит, але безрезультатно - цього кроку вже немає, але поганий вибір / ефективність - ні. Я також побіг sp_updatestatsцього ранку, щоб переконатися, що це не було причиною.

Я додав два плани виконання - коротший - і швидший. Крім того, ось питання, про який йдеться (варто зазначити, що SELECT, який я включив, здається, швидкий у випадках як малого, так і великого числа рядків):

    update top (10000) FactSubscriberUsage3
               set AccountID = sma.CustomerID
    --select top 50 f.AccountID, sma.CustomerID
      from FactSubscriberUsage3 f
      join dimTime t
        on f.TimeID = t.TimeID
      join #mac sma
        on f.macid = sma.macid
       and t.TimeValue between sma.StartDate and sma.enddate 
     where f.AccountID = 0 --There's a filtered index on the table for this

Ось швидкий план : План швидкого виконання

А ось повільніший : План повільного виконання

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

Тим, хто запитував статистичну версію об’єктів бази даних: Я навіть не розумів, що ти можеш це зробити, але це має повний сенс! Я намагався генерувати скрипти для бази даних, що мають лише статистику, щоб інші могли перевірити плани виконання для себе, але я можу генерувати статистику / гістограми на моєму відфільтрованому індексі (помилка синтаксису в скрипті, здається), тому я від удачі там. Я спробував зняти фільтр, і плани запитів були близькими, але не зовсім однаковими, і я не хочу нікого надсилати на гусячу погоню.

Оновлення та ще кілька повних планів виконання: Перш за все, SQL Sentry's Plan Explorer - це неймовірний інструмент. Я навіть не знав, що він існує, поки не переглянув інші запитання щодо плану запитів на цьому веб-сайті, і мені було досить багато сказати про те, як виконуються мої запити. Хоча я не впевнений, як вирішити проблему, вони дали зрозуміти, у чому проблема.

Ось підсумок 10, 100 та 1000 рядків - ви можете бачити, що запит у 1000 рядків - це спосіб, виходячи з решти інших: Підсумок заяви

Ви можете бачити, що третій запит має смішну кількість прочитаних, тому він, очевидно, робить щось зовсім інше. Ось передбачуваний план виконання з підрахунками рядків. Розрахунковий план виконання на 1000 рядків: План з розрахунку на виконання 1000 рядків

І ось фактичні результати плану виконання (до речі, під "ніколи не закінчується", виявляється, я мав на увазі "закінчується за годину"). Фактичний план виконання 1000 рядків Фактичний план виконання 1000 рядків

Перше , що я помітив, що, замість того , щоб тягнути 60 тисяч рядків з таблиці dimTime , як він очікує, що це на самому справі тягне 1,6 млрд, з B . Дивлячись на мій запит, я не впевнений, як це відтягує стільки рядків із таблиці dimTime. Оператор BETWEEN, який я використовую, просто гарантує, що я витягую правильний запис з #mac на основі запису часу в таблиці Фактів. Однак, коли я додаю рядок до пункту WHERE, де я фільтрую t.TimeValue (або t.TimeID) до одного значення, я можу успішно оновити 100000 рядків за лічені секунди. В результаті цього, і як було зрозуміло в планах виконання, які я включив, очевидно, що проблема в моєму графіку роботи, але я не впевнений, як я змінив би критерії приєднання, щоб вирішити цю проблему і підтримувати точність . Будь-які думки?

Для довідки, ось план (з кількістю рядків) для оновлення 100 рядків. Ви можете бачити, що він потрапляє в той самий індекс і все ще з тоною рядків, але ніде не має однакової величини проблеми. Виконання 100 рядків із кількістю ліній : введіть тут опис зображення


Це GOTTA Be статистика. Ви бігли sp_updatestatisticsпо столу?
JNK

@JNK: Спочатку я так думав, але вже запустив sp_updatestats без змін. Я просто запустив його ще раз, і було небайдуже оновлювати статистику на будь-якому з індексів, що беруть участь у запиті. Дякую, хоча!
SqlRyan

Другий - це широкий (за індексом) план оновлення, а не вузький (за рядком), який пояснює деякі додаткові видимі складності. Але насправді єдиною різницею є приєднатися замовлення from #mac sma join f on f.macid = sma.macid join dimTime t on f.TimeID = t.TimeID and t.TimeValue between sma.StartDate and sma.enddateпротиfrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
Мартін Сміт

Щось тут не так. Навіть дорогий план запитів повинен генерувати рядки поступово. A TOP 50все ж слід швидко виконати. Чи можете ви завантажити плани XML? Мені потрібно переглянути кількість рядків. Чи можете ви запустити TOP 50з maxdop 1 і як вибір, а не як оновлення та розмістити план? (Спрощення спрощення / розбиття простору пошуку).
usr

Приєднання @usr t.TimeValue between sma.StartDate and sma.enddateможе в кінцевому підсумку створити набагато більше марних рядків, які згодом відфільтруються в ході з'єднання проти FactSubscriber і тому не закінчуються в кінцевому результаті.
Мартін Сміт

Відповіді:


3

Індекс на dimTime змінюється. Швидший план використовує індекс _dta. Спочатку переконайтеся, що він не позначений як гіпотетичний індекс у sys.indexes.

Думаючи, що ви можете обійти деяку параметризацію, використовуючи таблицю #mac для фільтрації, а не просто надавати дати початку / кінця, подібні цій ГДЕ t.TimeValue між @StartDate та @enddate. Позбавтеся від цієї темп-таблиці.


Попередньо встановлений індекс dta виглядає так, як його було створено за дотриманням рекомендації DTA без налаштування імені. Гіпотетичні індекси не можуть відображатися у фактичних планах виконання (і не оцінюються без деяких недокументованих команд). Не знаєте, як працюватиме ваше друге пропозиція. t.TimeValue between sma.StartDate and sma.enddateспіввідноситься, тому може змінюватися для кожного рядка #tempтаблиці. Чим ОП замінить його?
Мартін Сміт

Справедливо кажучи, я не приділяв належної уваги темп-таблиці.
william_a_dba

1
Однак гіпотетичні показники дійсно можуть скласти план виконання. Якщо це гіпотетично, його слід скинути і відтворити. blogs.technet.com/b/anurag_sharma/archive/2008/04/15/…
william_a_dba

Гіпотетичні індекси залишаються, коли DTA не закінчується / заморожується до завершення. Ви повинні їх очистити вручну, якщо є гикавка з DTA.
william_a_dba

1
@william_a_dba - Ах, я бачу, що ти маєш на увазі зараз (прочитавши посилання). Запит ніколи не закінчується, він міг би його постійно перекомпонувати. Цікаво!
Мартін Сміт

1

Без додаткової інформації про кількість рядків у плані, моя попередня рекомендація - організувати правильний порядок приєднання у запиті та примусити його використовувати OPTION (FORCE ORDER). Забезпечити порядок приєднання першого плану.

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