Планове за изпълнение
По-сложно е, отколкото бихте очаквали да разберете от информацията, предоставена в плановете за изпълнение, ако SQL израз използва проста параметризация . Не е изненада, че дори опитните потребители на SQL Server са склонни да разбират това погрешно, като се има предвид противоречивата информация, която често ни се предоставя.
Нека разгледаме някои примери с помощта на базата данни Stack Overflow 2010 на SQL Server 2019 CU 14, като съвместимостта на базата данни е зададена на 150.
За да започнем, ще ни трябва нов неклъстериран индекс:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1. Приложена проста параметризация
Тази първа примерна заявка използва проста параметризация :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
Прогнозното (предварително изпълнение) планът има следните елементи, свързани с параметризацията:
Прогнозни свойства за параметризиране на плана
Обърнете внимание на @1
параметърът се въвежда навсякъде с изключение на текста на заявката, показан в горната част.
действителното (след изпълнение) планът има:
Свойства за параметриране на действителен план
Забележете, че прозорецът със свойства вече е загубил ParameterizedText
елемент, докато получавате информация за стойността на параметъра по време на изпълнение. Параметризираният текст на заявката вече се показва в горната част на прозореца с „@1
“ вместо „999“.
2. Проста параметризация не е приложена
Този втори примерне използвайте проста параметризация:
-- Projecting an extra column SELECT U.DisplayName, U.CreationDate -- NEW FROM dbo.Users AS U WHERE U.Reputation = 999;
Прогнозното планът показва:
Прогнозен непараметризиран план
Този път параметърът @1
липсва в Търсене на индекс подсказка, но параметризираният текст и другите елементи на списъка с параметри са същите като преди.
Нека да разгледаме действителните план за изпълнение:
Действителен непараметризиран план
Резултатите са същите като предишните параметризирани действителни план, освен сега Търсене на индекс подсказката показва непараметризираната стойност „999“. Текстът на заявката, показан отгоре, използва @1
маркер за параметър. Прозорецът със свойства също използва @1
и показва стойността на параметъра по време на изпълнение.
Заявката не е параметризиран израз въпреки всички доказателства за противното.
3. Неуспешна настройка на параметрите
Третият ми пример също ене параметризирани от сървъра:
-- LOWER function used SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999;
Прогнозното планът е:
Неуспешно параметризиране на прогнозния план
Няма споменаване на @1
параметър навсякъде сега и Списък с параметри липсва раздел от прозореца със свойства.
действителното планът за изпълнение е същият, така че няма да си правя труда да го показвам.
4. Паралелен параметризиран план
Искам да ви покажа още един пример за използване на паралелизъм в плана за изпълнение. Ниската прогнозна цена на моите тестови заявки означава, че трябва да намалим прага на разходите за паралелизъм до 1:
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 1; RECONFIGURE;
Този път примерът е малко по-сложен:
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC;
Прогнозното планът за изпълнение е:
Прогнозен паралелен параметризиран план
Текстът на заявката в горната част остава непараметризиран, докато всичко останало е. Сега има два маркера за параметри, @1
и @2
, тъй като проста параметризация намери две подходящи буквални стойности.
действителното планът за изпълнение следва модела от пример 1:
Действителен паралелен параметризиран план
Текстът на заявката в горната част вече е параметризиран и прозорецът със свойства съдържа стойности на параметрите по време на изпълнение. Този паралелен план (с a Сортиране оператор) определено се параметризира от сървъра с помощта на проста параметризация .
Надеждни методи
Има причини за всички показани досега поведения и още няколко. Ще се опитам да обясня много от тях в следващата част от тази поредица, когато разглеждам компилацията на планове.
Междувременно ситуацията с showplan като цяло и SSMS в частност не е идеална. Това е объркващо за хората, които работят със SQL Server през цялата си кариера. На кои маркери за параметри имате доверие и кои пренебрегвате?
Има няколко надеждни метода за определяне дали към даден конкретен оператор е приложена проста параметризация успешно или не.
Съхранение на заявки
Ще започна с един от най-удобните, магазинът за заявки. За съжаление, не винаги е толкова просто, колкото може да си представите.
Трябва да активирате функцията за съхранение на заявки за контекста на базата данни където се изпълнява операторът и OPERATION_MODE
трябва да бъде настроен на READ_WRITE
, което позволява на хранилището за заявки да събира активно данни.
След изпълнение на тези условия, изходът на showplan след изпълнение съдържа допълнителни атрибути, включително StatementParameterizationType . Както подсказва името, това съдържа код, описващ типа параметризация, използвана за израза.
Вижда се в прозореца със свойства на SSMS, когато е избран основният възел на план:
StatementParameterizationType
Стойностите са документирани в sys.query_store_query
:
- 0 – Няма
- 1 – Потребител (изрично параметризиране)
- 2 – Проста параметризация
- 3 – Принудително параметриране
Този полезен атрибут се появява в SSMS само когато е действителен планът е заявен и липсва, когато е оценен планът е избран. Важно е да запомните, че планът трябва да се кешира . Заявка за приблизителна планът от SSMS не кешира произведения план (от SQL Server 2012).
След като планът е кеширан, StatementParameterizationType се появява на обичайните места, включително чрез sys.dm_exec_query_plan
.
Можете също така да се доверите на други места, типът параметризация на който се записва в хранилището на заявки, като например query_parameterization_type_desc
колона в sys.query_store_query
.
Едно важно предупреждение. Когато заявката съхранява OPERATION_MODE
е настроен на READ_ONLY
, StatementParameterizationType атрибутът все още се попълва в SSMS действителен планове, но винаги е нула — създава погрешно впечатление, че изявлението не е параметризирано, когато може да е било така.
Ако сте доволни да активирате хранилището на заявки, сте сигурни, че е четене-запис, и разглеждате само плановете след изпълнение в SSMS, това ще работи за вас.
Предикати за стандартен план
Текстът на заявката, показан в горната част на прозореца на графичния план за показване в SSMS, не е надежден, както показват примерите. Нито можете да разчитате на ParameterList се показва в Свойства прозорец, когато е избран основният възел на плана. Параметризиран текст атрибут, показан за приблизително само планове също не е окончателно.
Можете обаче да разчитате на свойствата, свързани с индивидуалните оператори на план. Дадените примери показват, че те присъстват в подсказките за инструменти когато задържите курсора на мишката върху оператор.
Предикат, съдържащ маркер за параметър като @1
или @2
показва параметризиран план. Операторите, които най-вероятно ще съдържат параметър, са Сканиране на индекса , Търсене в индекс и Филтър .
Предикати с маркери за параметри
Ако номерирането започва с @1
, използва проста параметризация . Принудителното параметризиране започва с @0
. Трябва да спомена, че документираната тук схема за номериране подлежи на промяна по всяко време:
Предупреждение за промяна
Въпреки това, това е методът, който използвам най-често, за да се определи дали даден план е бил обект на параметризиране от страна на сървъра. Обикновено е бързо и лесно да проверите визуално план за предикати, съдържащи маркери на параметри. Този метод работи и за двата типа планове, приблизителни и действителни .
Обекти за динамично управление
Има няколко начина за запитване на кеша на плана и свързаните DMO, за да определите дали даден израз е параметризиран. Естествено, тези заявки работят само върху планове в кеша, така че изявлението трябва да е било изпълнено до завършване, кеширано и впоследствие да не е изгонено по някаква причина.
Най-директният подход е да потърсите Adhoc планирайте, като използвате точно SQL текстово съвпадение с изявлението, което представлява интерес. Adhoc планът ще бъде обвивка съдържащ ParameterizedPlanHandle ако операторът е параметризиран от сървъра. След това манипулаторът на плана се използва за намиране на Подготвен план. Adhoc планът няма да съществува, ако оптимизирането за ad hoc работни натоварвания е активирано и въпросният оператор е изпълнен само веднъж.
Този тип запитване често завършва с раздробяване на значително количество XML и сканиране на целия кеш на плана поне веднъж. Също така е лесно да сбъркате кода, не на последно място защото плановете в кеша обхващат цяла партида. Една партида може да съдържа множество оператори, всеки от които може или не може да бъде параметризиран. Не всички DMO работят с една и съща детайлност (партида или изявление), което прави лесно отстраняването.
По-долу е показан ефективен начин за изброяване на изявления, представляващи интерес, заедно с фрагменти от план само за тези отделни изявления:
SELECT StatementText = SUBSTRING(T.[text], 1 + (QS.statement_start_offset / 2), 1 + ((QS.statement_end_offset - QS.statement_start_offset) / 2)), IsParameterized = IIF(T.[text] LIKE N'(%', 'Yes', 'No'), query_plan = TRY_CONVERT(xml, P.query_plan) FROM sys.dm_exec_query_stats AS QS CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T CROSS APPLY sys.dm_exec_text_query_plan ( QS.plan_handle, QS.statement_start_offset, QS.statement_end_offset) AS P WHERE -- Statements of interest T.[text] LIKE N'%DisplayName%Users%' -- Exclude queries like this one AND T.[text] NOT LIKE N'%sys.dm%' ORDER BY QS.last_execution_time ASC, QS.statement_start_offset ASC;
За да илюстрираме, нека стартираме една партида, съдържаща четирите примера от по-рано:
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO -- Example 1 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 2 SELECT U.DisplayName, U.CreationDate FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 4 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC; GO
Резултатът от DMO заявката е:
Изход на DMO заявка
Това потвърждава, че само примери 1 и 4 са били успешно параметризирани.
Броячи на производителност
Възможно е да използвате броячите на ефективността на SQL Statistics, за да получите подробна представа за дейността по параметризиране и за двете оценени и действителни планове. Използваните броячи не са с обхват на сесия, така че ще трябва да използвате тестов екземпляр без друга едновременна дейност, за да получите точни резултати.
Ще допълня информацията за брояча на параметризацията с данни от sys.dm_exec_query_optimizer_info
DMO за предоставяне на статистически данни и за тривиални планове.
Необходима е известна грижа, за да се предотврати промяната на самите изявления, които четат информацията за брояча. Ще се справя с това, като създам няколко временни съхранени процедури:
CREATE PROCEDURE #TrivialPlans AS SET NOCOUNT ON; SELECT OI.[counter], OI.occurrence FROM sys.dm_exec_query_optimizer_info AS OI WHERE OI.[counter] = N'trivial plan'; GO CREATE PROCEDURE #PerfCounters AS SET NOCOUNT ON; SELECT PC.[object_name], PC.counter_name, PC.cntr_value FROM sys.dm_os_performance_counters AS PC WHERE PC.counter_name LIKE N'%Param%';
След това скриптът за тестване на конкретен израз изглежда така:
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO EXECUTE #PerfCounters; EXECUTE #TrivialPlans; GO SET SHOWPLAN_XML ON; GO -- The statement(s) under test: -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; GO SET SHOWPLAN_XML OFF; GO EXECUTE #TrivialPlans; EXECUTE #PerfCounters;
Коментирайте SHOWPLAN_XML
пакети, за да стартирате целевия(и) израз(и) и да получите действителен планове. Оставете ги на място за оценка планове за изпълнение.
Изпълнението на цялото нещо, както е написано, дава следните резултати:
Резултати от теста на брояча на производителността
По-горе подчертах къде стойностите са се променили при тестване на пример 3.
Увеличението на брояча на „тривиален план“ от 1050 на 1051 показва, че е намерен тривиален план за тестовото изявление.
Броячите за проста параметризация се увеличиха с 1 както за опити, така и за неуспехи, показвайки, че SQL Server се е опитал да параметризира израза, но не успя.
Край на част 3
В следващата част от тази поредица ще обясня любопитните неща, които видяхме, като опиша как просто параметризиране и тривиални планове взаимодействат с процеса на компилация.
Ако сте променили своя разходен праг за паралелизъм за да стартирате примерите, не забравяйте да го нулирате (моят беше настроен на 50):
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;