Як оптимізувати T-SQL-запит за допомогою плану виконання


15

У мене є SQL-запит, який я витратив останні два дні, намагаючись оптимізувати, використовуючи пробну помилку та план виконання, але безрезультатно. Вибачте, будь ласка, за це, але я опублікую весь план виконання тут. Я доклав зусиль, щоб назви таблиць і стовпців у плані запитів та виконання були загальними як для стислості, так і для захисту IP-адреси моєї компанії. План виконання можна відкрити за допомогою провідника SQL Sentry Plan .

Я зробив досить багато T-SQL, але використання планів виконання для оптимізації мого запиту є для мене новою областю, і я дійсно намагався зрозуміти, як це зробити. Тож, якщо хтось міг би мені допомогти у цьому і пояснити, як можна виконати розшифровку цього плану виконання, щоб знайти шляхи в запиті для його оптимізації, я був би вічно вдячний. У мене є ще багато запитів для оптимізації - мені просто потрібен плацдарм, щоб допомогти мені з цим першим.

Це запит:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

Я знайшов, що третє твердження (коментується як повільне) - це та частина, яка займає найбільше часу. Дві заяви перед поверненням майже миттєво.

План виконання доступний як XML за цим посиланням .

Краще клацнути правою кнопкою миші та зберегти, а потім відкрити в SQL Sentry Plan Explorer або іншому програмному забезпеченні для перегляду, а не відкривати у своєму браузері.

Якщо вам потрібна додаткова інформація про таблиці чи дані, будь ласка, не соромтеся запитати.


2
Ваша статистика не виходить. Коли в останній раз ви дефрагментовані індекси чи оновлені статистичні дані? Крім того, я б спробував використовувати таблицю temp замість змінної таблиці @MyTableVar, оскільки оптимізатор дійсно не може використовувати статистику змінних таблиць.
Адам Хайнс

Дякую за вашу відповідь, Адам. Зміна @MyTableVar до темп-таблиці не впливає, але це лише невелика кількість рядків (що видно з плану виконання). Що в плані виконання показує, що мої статистичні дані відсторонені? Чи вказується, які індекси мають бути реорганізовані або перебудовані, а в яких таблицях (іх) слід оновлювати статистику?
Нео

3
Це приєднання хешу в нижньому правому куті має приблизно 24000 рядків у введенні збірки, але фактичне 3 285 620, таким чином, цілком може бути пролито tempdb. тобто оцінки для рядків, що виникають в результаті з'єднання між ними, TableAі @MyTableVarє вихідними. Також кількість рядків, що надходять у сорти, набагато більша, ніж передбачувана, щоб вони також могли бути проливними.
Мартін Сміт

Відповіді:


22

Перш ніж перейти до основної відповіді, є два фрагменти програмного забезпечення, які потрібно оновити.

Необхідні оновлення програмного забезпечення

Перший - SQL Server. Ви працюєте з пакетом оновлень 1 для сервісу SQL Server 2008 (збірка 2531). Вам слід зафіксувати принаймні поточний пакет оновлень (SQL Server 2008 Service Pack 3 - збірка 5500). Найновішою збіркою SQL Server 2008 під час написання є Service Pack 3, Cumulative Update 12 (збірка 5844).

Друга частина програмного забезпечення - SQL Sentry Plan Explorer . Останні версії мають суттєві нові функції та виправлення, включаючи можливість безпосередньо завантажувати план запитів для експертного аналізу (не потрібно вставляти XML нікуди!)

Аналіз плану запитів

Оцінка кардинальності для змінної таблиці абсолютно правильна, завдяки перекомпіляції на рівні оператора:

таблична змінна оцінка

На жаль, змінні таблиці не підтримують статистику розподілу, тому все, що оптимізатор знає, - це шість рядків; він нічого не знає про значення, які можуть бути в цих шести рядках. Ця інформація є вирішальною, враховуючи, що наступна операція - це приєднання до іншої таблиці. Оцінка кардинальності від цього приєднання базується на дикій здогадці оптимізатора:

перша оцінка приєднання

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

SQL Server 2008 не виділяє цього в планах виконання; ви можете відстежувати розливи, використовуючи розширені події або попередження про сортування профілів та попередження хешу . Пам'ять зарезервована для сортування та хешей на основі оцінок кардинальності перед початком виконання, і не може бути збільшена під час виконання незалежно від того, скільки запасної пам'яті може мати ваш SQL Server. Точні оцінки кількості рядків мають вирішальне значення для будь-якого плану виконання, що включає операції, що споживають пам'ять робочої області.

Ваш запит також параметризований. Вам слід розглянути можливість додавання OPTION (RECOMPILE)до запиту, якщо різні значення параметрів впливають на план запиту. Вам, мабуть, варто подумати про його використання в будь-якому випадку, щоб оптимізатор міг бачити значення @Param1під час компіляції. Якщо нічого іншого, це може допомогти оптимізатору зробити більш обґрунтовану оцінку для пошуку індексу, показаного вище, враховуючи, що таблиця дуже велика та розділена. Він також може включити статичне усунення розділів.

Спробуйте знову запит із тимчасовою таблицею замість змінної таблиці та OPTION (RECOMPILE) . Ви також повинні спробувати матеріалізувати результат першого приєднання до іншої тимчасової таблиці та запустити решту запиту проти цього. Кількість рядків не настільки велика (3 285 620), тому це повинно бути досить швидким. Оптимізатор матиме точну оцінку кардинальності та статистику розподілу за результатом приєднання. При удачі, решта плану буде приємно встати на місце.

Працюючи з властивостей, показаних на плані, матеріалізуючим запитом буде:

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

Ви також можете INSERTввести попередньо визначену тимчасову таблицю (правильні типи даних не відображаються в плані, тому я не можу виконати цю частину). Нова тимчасова таблиця може або не мати вигоди від кластерних та некластеризованих індексів.


Дуже дякую за цю глибоку відповідь. Вибачте, що потрібен тиждень, щоб відповісти - я працюю над цим щодня, переплітаючись з іншими роботами. Я реалізував ваші пропозиції, що матеріалізують приєднання до TableA #AnotherTempTable. Це, мабуть, мало найкращий вплив - інші пропозиції (використання темп-таблиці замість змінної таблиці для @MyTableVar та використання OPTION (RECOMPILE)не мало особливого ефекту чи взагалі ніяких ефектів. "Анонімізувати" та "Опублікувати на SQLPerformance.com" варіанти в SQL Sentry Plan Explorer чудові - я тільки що їх використовував: answer.sqlperformance.com/questions/1087
Нео

-6

Я зауважую, що на @MyTableVar має бути ПК, і я погоджуюся, що #MyTableVar часто краще працює (особливо з більшою кількістю рядків).

Умова в пункті де

   WHERE A.A_Var8_FK_LK = @Param1

слід перейти до внутрішнього з'єднання A AND'ed. На моєму досвіді оптимізатор недостатньо розумний, щоб зробити це (вибачте, що не подивився на план), і це може зробити величезну зміну.

Якщо ці зміни не показують поліпшення, я б потім створив ще одну темп-таблицю A і всі речі, до яких вона приєднується до обмежених (красиво?) Від A.A_Var8_FK_LK = @ Param1, якщо це групування має для вас логічний сенс.

Потім створіть кластерний індекс у цій таблиці темпів (до або після створення) для наступної умови приєднання.

Потім приєднайте цей результат до кількох таблиць (F і T), які залишилися.

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

Слід може показувати розлиття tempdb, що може чи не матиме різкого впливу.

Інший альтернативний підхід - який швидше спробувати принаймні - це впорядкувати таблиці від найменшої кількості рядків (А) до найвищої, а потім почати додавати об'єднання, хеш і цикл до об'єднань. При наявності підказок порядок приєднання фіксується, як зазначено. Інші користувачі з розумом уникають такого підходу, оскільки це може завдати шкоди в довгостроковій перспективі, якщо відносна кількість рядків різко зміниться. Бажана мінімальна кількість підказок.

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


Так. Це забезпечує, що рядки, повернені A, обмежені обмеженням. Інакше оптимізатор може приєднатися спочатку і застосувати обмеження пізніше. Я маю справу з цим щодня.
crokusek

4
@crokusek Ви просто помиляєтесь. Оптимізатор SQL-сервера досить добре знає, що запити еквівалентні (будь-яка умова в WHERE або в пункті ON), коли це приєднання INNER.
ypercubeᵀᴹ

6
Можливо, для оптимізатора запитів буде корисна серія Пола Уайта .
Мартін Сміт

Це жахлива звичка. Можливо, це буде саме для цього конкретного випадку (де є одне обмеження), але я приходжу з країни багатьох розробників, що складаються на AND умовах у пункті where. SQL Server робить НЕ послідовно «крок» їх назад приєднатися до вас.
crokusek

Погодьтеся неправильно для зовнішнього (і правого з'єднання). Але коли в пункті де є лише вирази AND'd і кожен термін відповідає виключно конкретному внутрішньому об'єднанню, цей термін може надійно і впевнено переміщуватися в місце "ввімкнення" як оптимізація та найкраща практика (imo). Будь то "справжня" умова приєднання або просто фіксований обмеження є вторинним для великого підвищення продуктивності. Це посилання стосується тривіального випадку. У реальному житті багато разів, коли умови з конвертацією () і математикою, і це робить їх кращими кандидатами, з яких можна отримати найкращі практики.
crokusek
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.