Планове за изпълнение
По-сложно е, отколкото бихте очаквали да разберете от информацията, предоставена в плановете за изпълнение, ако 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;