Това е тринадесетата и последна част от поредицата за таблични изрази. Този месец продължавам дискусията, която започнах миналия месец за вградените функции с таблично стойности (iTVFs).
Миналия месец обясних, че когато SQL Server вгражда iTVF, които са запитани с константи като входни данни, той прилага оптимизация за вграждане на параметри по подразбиране. Вграждането на параметри означава, че SQL Server замества препратките към параметрите в заявката с буквалните константни стойности от текущото изпълнение и след това кодът с константите се оптимизира. Този процес позволява опростяване, което може да доведе до по-оптимални планове за заявки. Този месец разглеждам темата по-подробно, обхващайки конкретни случаи за такива опростявания като постоянно сгъване и динамично филтриране и подреждане. Ако имате нужда от освежаване относно оптимизацията за вграждане на параметри, разгледайте статията от миналия месец, както и отличната статия на Пол Уайт. Параметър Sniffing, Embedding, and the RECOMPILE Options.
В моите примери ще използвам примерна база данни, наречена TSQLV5. Можете да намерите скрипта, който го създава и попълва тук, и неговата ER диаграма тук.
Постоянно сгъване
По време на ранните етапи на обработка на заявки, SQL Server оценява определени изрази, включващи константи, сгъвайки ги до константите на резултата. Например изразът 40 + 2 може да бъде сгънат до константата 42. Можете да намерите правилата за сгъваеми и несгъваеми изрази тук под „Оценка на константата и израза“.
Интересното по отношение на iTVF е, че благодарение на оптимизацията за вграждане на параметри, заявките, включващи iTVF, където предавате константи, тъй като входните данни могат, при правилните обстоятелства, да се възползват от постоянното сгъване. Познаването на правилата за сгъваеми и несгъваеми изрази може да повлияе на начина, по който прилагате вашите iTVF. В някои случаи, като приложите много фини промени към изразите си, можете да активирате по-оптимални планове с по-добро използване на индексирането.
Като пример, разгледайте следната реализация на iTVF, наречена Sales.MyOrders:
ИЗПОЛЗВАЙТЕ TSQLV5;GO СЪЗДАВАТЕ ИЛИ ПРОМЕНЯТЕ ФУНКЦИЯ Sales.MyOrders ( @add AS INT, @subtract AS INT )ВРЪЩА TABLEASRETURN SELECT orderid + @add - @subtract AS myorderid, orderdate, custid, empid FROM SalesИздадете следната заявка, включваща iTVF (ще наричам това като заявка 1):
ИЗБЕРЕТЕ myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248) ORDER BY myorderid;Планът за заявка 1 е показан на фигура 1.
Фигура 1:План за заявка 1
Клъстерираният индекс PK_Orders се дефинира с orderid като ключ. Ако се случи постоянно сгъване тук след вграждане на параметър, изразът за подреждане orderid + 1 – 10248 щеше да бъде сгънат до orderid – 10247. Този израз би се считал за запазващ реда израз по отношение на orderid и като такъв би позволил оптимизатор да разчита на реда на индексите. Уви, това не е така, както е видно от изричния оператор Sort в плана. И какво стана?
Постоянните правила за сгъване са придирчиви. Изразът колона1 + константа1 – константа2 се оценява отляво надясно за целите на постоянното сгъване. Първата част, колона1 + константа1 не е сгъната. Нека наречем този израз 1. Следващата част, която се оценява, се третира като израз1 – константа2, която също не се сгъва. Без сгъване, израз във формата колона1 + константа1 – константа2 не се счита за запазващ реда по отношение на колона1 и следователно не може да разчита на подреждане на индекси, дори ако имате поддържащ индекс на колона1. По същия начин изразът константа1 + колона1 – константа2 не е константа сгъваема. Въпреки това изразът константа1 – константа2 + колона1 е сгъваем. По-конкретно, първата част константа1 – константа2 се сгъва в една константа (нека я наречем константа3), което води до израза константа3 + колона1. Този израз се счита за израз, запазващ реда по отношение на колона 1. Така че стига да сте сигурни, че сте написали израза си с помощта на последния формуляр, можете да разрешите на оптимизатора да разчита на подреждане на индекси.
Помислете за следните заявки (ще ги наричам заявка 2, заявка 3 и заявка 4) и преди да разгледате плановете за заявка, вижте дали можете да разберете кое ще включва изрично сортиране в плана и кое не:
-- Заявка 2SELECT orderid + 1 - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Заявка 3SELECT 1 + orderid - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Заявка 4SELECT 1 - 10248 + orderid AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid;Сега разгледайте плановете за тези заявки, както е показано на Фигура 2.
Фигура 2:Планове за заявка 2, заявка 3 и заявка 4
Разгледайте операторите Compute Scalar в трите плана. Само планът за заявка 4 имаше постоянно сгъване, което води до израз за подреждане, който се счита за запазващ реда по отношение на orderid, избягвайки изричното сортиране.
Разбирайки този аспект на постоянното сгъване, можете лесно да коригирате iTVF, като промените израза orderid + @add – @subtract на @add – @subtract + orderid, така:
СЪЗДАВАНЕ ИЛИ ПРОМЕНЯНЕ НА ФУНКЦИЯ Sales.MyOrders ( @add AS INT, @subtract AS INT )ВЪЗРАЩА TABLEASRETURN SELECT @add - @subtract + orderid КАТО myorderid, orderdate, custid, empid FROM Sales.Orders;GOЗаявете отново функцията (ще наричам това като заявка 5):
ИЗБЕРЕТЕ myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248) ORDER BY myorderid;Планът за тази заявка е показан на Фигура 3.
Фигура 3:План за заявка 5
Както можете да видите, този път заявката имаше постоянно сгъване и оптимизаторът успя да разчита на подреждане на индекси, избягвайки изричното сортиране.
Използвах прост пример, за да демонстрирам тази техника за оптимизиране и като такава може да изглежда малко измислена. Можете да намерите практическо приложение на тази техника в статията Решения за предизвикателство за генератор на номера – част 1.
Динамично филтриране/подреждане
Миналия месец разгледах разликата между начина, по който SQL Server оптимизира заявка в iTVF спрямо същата заявка в съхранена процедура. SQL Server обикновено прилага оптимизация за вграждане на параметри по подразбиране за заявка, включваща iTVF с константи като входни данни, но оптимизира параметризираната форма на заявка в съхранена процедура. Въпреки това, ако добавите OPTION(RECOMPILE) към заявката в съхранената процедура, SQL Server обикновено ще прилага оптимизация за вграждане на параметри и в този случай. Предимствата в случая iTVF включват факта, че можете да го включите в заявка и стига да предавате повтарящи се постоянни входове, има потенциал да използвате повторно кеширан план. Със съхранена процедура не можете да я включите в заявка и ако добавите OPTION(RECOMPILE), за да получите оптимизация за вграждане на параметри, няма възможност за повторно използване на плана. Съхранената процедура позволява много по-голяма гъвкавост по отношение на кодовите елементи, които можете да използвате.
Нека видим как всичко това се изпълнява в класическа задача за вграждане и подреждане на параметри. Следва опростена съхранена процедура, която прилага динамично филтриране и сортиране, подобно на това, което Пол използва в статията си:
СЪЗДАДЕТЕ ИЛИ ПРОМЕНЯТЕ ПРОЦЕДУРА HR.GetEmpsP @lastnamepattern КАТО NVARCHAR(50), @sort AS TINYINTASSET NOCOUNT ON; ИЗБЕРЕТЕ empid, име, фамилия ОТ HR.EmployeesWHERE фамилно име КАТО @lastnamepattern ИЛИ @lastnamepattern Е NULLORDER ОТ СЛУЧА, КОГАТО @sort =1 ТОГАВА empid END, CASE WHEN @sort =2 THEN име END, GO фамилно име END, CASE THEN WHEN;Забележете, че текущата реализация на съхранената процедура не включва OPTION(RECOMPILE) в заявката.
Помислете за следното изпълнение на съхранената процедура:
EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3;Планът за това изпълнение е показан на Фигура 4.
Фигура 4:План за процедура HR.GetEmpsP
В колоната за фамилното име е дефиниран индекс. Теоретично, с текущите входове, индексът може да бъде полезен както за нуждите на заявката за филтриране (с търсене), така и за подреждане (с подредено:истинско сканиране на диапазона). Въпреки това, тъй като по подразбиране SQL Server оптимизира параметризираната форма на заявката и не прилага вграждане на параметри, той не прилага необходимите опростявания, за да може да се възползва от индекса както за филтриране, така и за целите на подреждането. Така че планът е за многократна употреба, но не е оптимален.
За да видите как нещата се променят с оптимизацията за вграждане на параметри, променете заявката за съхранена процедура, като добавите OPTION(RECOMPILE), така:
СЪЗДАДЕТЕ ИЛИ ПРОМЕНЯТЕ ПРОЦЕДУРА HR.GetEmpsP @lastnamepattern КАТО NVARCHAR(50), @sort AS TINYINTASSET NOCOUNT ON; ИЗБЕРЕТЕ empid, име, фамилно имеFROM HR.EmployeesWHERE фамилно име КАТО @lastnamepattern ИЛИ @lastnamepattern Е NULLORDER ОТ СЛУЧАЙ, КОГАТО @sort =1 ТОГАВА empid END, CASE WHEN @sort =2 THEN фамилия END, CASE КОГАТО ИМЕ КОГАТО @tREEND3 );GOИзпълнете отново съхранената процедура със същите входове, които сте използвали преди:
EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3;Планът за това изпълнение е показан на Фигура 5.
Фигура 5:План за процедура HR.GetEmpsP С ОПЦИЯ(РЕКОМПИЛИРАНЕ)
Както можете да видите, благодарение на оптимизацията за вграждане на параметри, SQL Server успя да опрости предиката на филтъра до фамилното име на предиката sargable LIKE N'D%' и списъка за подреждане до NULL, NULL, фамилия. И двата елемента биха могли да се възползват от индекса на фамилното име и следователно планът показва търсене в индекса и няма изрично сортиране.
Теоретично очаквате да можете да получите подобно опростяване, ако внедрите заявката в iTVF и следователно подобни ползи за оптимизация, но с възможността за повторно използване на кеширани планове, когато същите входни стойности се използват повторно. Така че, нека опитаме...
Ето опит за внедряване на същата заявка в iTVF (все още не изпълнявайте този код):
СЪЗДАВАНЕ ИЛИ ПРОМЕНЯНЕ НА ФУНКЦИЯ HR.GetEmpsF( @lastnamepattern КАТО NVARCHAR(50), @sort КАТО TINYINT)ВЪРНА ТАБЛИЦАВРЪЩАНЕ ИЗБЕРЕТЕ empid, име, фамилия ОТ HR.Служители КЪДЕ фамилно име КАТО @lastnamepatterennamePAOR n. @sort =1 THEN empid END, CASE WHEN @sort =2 THEN име END, CASE WHEN @sort =3 THEN фамилно име END;GOПреди да се опитате да изпълните този код, виждате ли проблем с тази реализация? Не забравяйте, че в началото на тази серия обясних, че изразът за таблица е таблица. Тялото на таблицата е набор (или мултинабор) от редове и като такъв няма ред. Следователно, обикновено заявка, използвана като израз на таблица, не може да има клауза ORDER BY. Всъщност, ако се опитате да изпълните този код, получавате следната грешка:
Съобщение 1033, Ниво 15, Състояние 1, Процедура GetEmps, Ред 16 [Пакетна начална линия 128]
Клаузата ORDER BY е невалидна в изгледи, вградени функции, извлечени таблици, подзаявки и общи изрази на таблици, освен ако TOP, OFFSET или FOR XML също е посочено.Разбира се, както казва грешката, SQL Server ще направи изключение, ако използвате филтриращ елемент като TOP или OFFSET-FETCH, който разчита на клаузата ORDER BY, за да дефинира аспекта на подреждане на филтъра. Но дори и да включите клауза ORDER BY във вътрешната заявка благодарение на това изключение, все още не получавате гаранция за реда на резултата във външна заявка спрямо табличния израз, освен ако няма своя собствена клауза ORDER BY .
Ако все пак искате да приложите заявката в iTVF, можете да накарате вътрешната заявка да обработва частта за динамично филтриране, но не и динамичното подреждане, както следва:
СЪЗДАВАНЕ ИЛИ ПРОМЕНЯНЕ НА ФУНКЦИЯ HR.GetEmpsF( @lastnamepattern КАТО NVARCHAR(50))ВЪРНА ТАБЛИЦАВРЪЩАНЕ ИЗБОР empid, име, фамилия ОТ HR.Служители КЪДЕТО фамилия КАТО @lastnamepattern ИЛИ @ISprenameNULLn;Разбира се, можете да накарате външната заявка да обработва всяка конкретна нужда от поръчка, като в следния код (ще наричам това като Заявка 6):
ИЗБЕРЕТЕ empid, име, фамилияFROM HR.GetEmpsF(N'D%')ПОРЪЧАЙТЕ ПО фамилно име;Планът за тази заявка е показан на фигура 6.
Фигура 6:План за заявка 6
Благодарение на вграждането и вграждането на параметри, планът е подобен на този, показан по-рано за заявката за съхранена процедура на фигура 5. Планът ефективно разчита на индекса както за целите на филтрирането, така и за подреждането. Въпреки това, вие не получавате гъвкавостта на въвеждането на динамично подреждане, както сте имали със съхранената процедура. Трябва да сте изрични с подреждането в клаузата ORDER BY в заявката към функцията.
Следният пример има заявка към функцията без филтриране и без изисквания за подреждане (ще наричам това като заявка 7):
ИЗБЕРЕТЕ empid, име, фамилияFROM HR.GetEmpsF(NULL);Планът за тази заявка е показан на Фигура 7.
Фигура 7:План за заявка 7
След вграждане и вграждане на параметри, заявката се опростява, за да няма предикат на филтъра и подреждане и се оптимизира с пълно неподредено сканиране на клъстерирания индекс.
И накрая, заявете функцията с N'D%' като шаблон за филтриране на входно фамилно име и подредете резултата по колоната за собствено име (ще наричам това като заявка 8):
ИЗБЕРЕТЕ empid, име, фамилияFROM HR.GetEmpsF(N'D%')ПОРЪЧАЙТЕ ПО собствено име;Планът за тази заявка е показан на Фигура 8.
Фигура 8:План за заявка 8
След опростяване, заявката включва само фамилното име на предиката за филтриране LIKE N'D%' и първо име на подреждащия елемент. Този път оптимизаторът избира да приложи неподредено сканиране на клъстерирания индекс с остатъчно предикатно фамилно име LIKE N'D%', последвано от изрично сортиране. Той избра да не прилага търсене в индекса за фамилно име, тъй като индексът не е покриващ, таблицата е толкова малка и подреждането на индекса не е от полза за текущите нужди за подреждане на заявки. Освен това няма дефиниран индекс в колоната с първо име, така че все пак трябва да се приложи изрично сортиране.
Заключение
Оптимизирането на iTVF за вграждане на параметри по подразбиране може също да доведе до постоянно сгъване, което позволява по-оптимални планове. Въпреки това, трябва да имате предвид постоянните правила за сгъване, за да определите как най-добре да формулирате изразите си.
Внедряването на логика в iTVF има предимства и недостатъци в сравнение с прилагането на логика в съхранена процедура. Ако не се интересувате от оптимизация за вграждане на параметри, оптимизацията по подразбиране на параметризирани заявки на съхранените процедури може да доведе до по-оптимално кеширане на планове и поведение при повторно използване. В случаите, когато се интересувате от оптимизация за вграждане на параметри, обикновено я получавате по подразбиране с iTVFs. За да получите тази оптимизация със съхранени процедури, трябва да добавите опцията за заявка RECOMPILE, но тогава няма да получите повторно използване на плана. Поне с iTVFs, можете да получите повторно използване на план, при условие че същите стойности на параметрите се повтарят. От друга страна, имате по-малко гъвкавост с елементите на заявката, които можете да използвате в iTVF; например не ви е позволено да имате клауза ORDER BY за презентация.
Обратно към цялата серия за изразите на таблици, намирам, че темата е изключително важна за практикуващите бази данни. По-пълната серия включва подсерията на генератора на числови серии, който е реализиран като iTVF. Общо поредицата включва следните 19 части:
- Основи на табличните изрази, част 1
- Основи на табличните изрази, част 2 – Производни таблици, логически съображения
- Основи на табличните изрази, част 3 – Производни таблици, съображения за оптимизиране
- Основи на табличните изрази, част 4 – Извлечени таблици, съображения за оптимизация, продължение
- Основи на табличните изрази, част 5 – CTE, логически съображения
- Основи на табличните изрази, част 6 – Рекурсивни CTE
- Основи на табличните изрази, част 7 – CTE, съображения за оптимизиране
- Основи на табличните изрази, част 8 – CTEs, съображенията за оптимизация продължават
- Основи на табличните изрази, част 9 – Изгледи, сравнени с извлечени таблици и CTE
- Основи на табличните изрази, част 10 – Изгледи, SELECT * и промени в DDL
- Основи на табличните изрази, част 11 – изгледи, съображения за модификация
- Основи на табличните изрази, част 12 – Вградени функции с таблично стойности
- Основи на табличните изрази, част 13 – Вградени функции с таблично стойности, продължение
- Предизвикателството е включено! Покана от общността за създаване на най-бързия генератор на серии от числа
- Решения за предизвикателство за генератор на номера – част 1
- Решения за предизвикателство за генератор на номера – част 2
- Решения за предизвикателство за генератор на номера – част 3
- Решения за предизвикателство за генератор на номера – част 4
- Решения за предизвикателство за генератор на номера – част 5