Utförandeplaner
Det är mer komplicerat än du kan förvänta dig att avgöra från informationen i exekveringsplanerna om en SQL-sats använder enkel parametrering . Det är ingen överraskning att även mycket erfarna SQL Server-användare tenderar att ha fel, med tanke på den motsägelsefulla informationen som ofta tillhandahålls oss.
Låt oss titta på några exempel som använder databasen Stack Overflow 2010 på SQL Server 2019 CU 14, med databaskompatibilitet inställd på 150.
För att börja behöver vi ett nytt icke-klustrat index:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1. Enkel parametrering tillämpad
Det här första exemplet använder enkel parametrering :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
Den uppskattade (förutförande) planen har följande parametriseringsrelaterade element:
Uppskattade planparameteregenskaper
Lägg märke till @1
parametern introduceras överallt förutom frågetexten som visas överst.
Den faktiska (efterutförande) planen har:
Faktiska planparameteregenskaper
Lägg märke till att egenskapsfönstret nu har förlorat ParameterizedText
element, samtidigt som du får information om parameterns körtidsvärde. Den parametriserade frågetexten visas nu överst i fönstret med '@1
' istället för '999'.
2. Enkel parametrering tillämpas inte
Det här andra exemplet inte använd enkel parametrering:
-- Projecting an extra column SELECT U.DisplayName, U.CreationDate -- NEW FROM dbo.Users AS U WHERE U.Reputation = 999;
Den uppskattade planen visar:
Uppskattad icke-parameteriserad plan
Denna gång parametern @1
saknas i Indexsökning verktygstips, men den parametriserade texten och andra parameterlistelement är desamma som tidigare.
Låt oss titta på det faktiska genomförandeplan:
Faktisk icke-parameteriserad plan
Resultaten är desamma som tidigare parametriserade faktiska plan, förutom nu Indexsökning verktygstips visar det icke-parameteriserade värdet '999'. Frågetexten som visas längst upp använder @1
parametermarkör. Egenskapsfönstret använder också @1
och visar körtidsvärdet för parametern.
Frågan är inte ett parametriserat uttalande trots alla bevis på motsatsen.
3. Parametrering misslyckades
Mitt tredje exempel är också inte parametrerad av servern:
-- LOWER function used SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999;
Den uppskattade planen är:
Parameterisering av uppskattad plan misslyckades
Det nämns inget om en @1
parametern var som helst nu, och Parameterlistan avsnittet i egenskapsfönstret saknas.
Den faktiska utförandeplanen är densamma, så jag tänker inte visa den.
4. Parallell parametriserad plan
Jag vill visa dig ytterligare ett exempel som använder parallellism i utförandeplanen. Den låga uppskattade kostnaden för mina testfrågor innebär att vi måste sänka kostnadströskeln för parallellitet till 1:
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 1; RECONFIGURE;
Exemplet är lite mer komplext den här gången:
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC;
Den uppskattade genomförandeplanen är:
Uppskattad parallell parameteriserad plan
Frågetexten längst upp förblir oparametriserad medan allt annat är det. Det finns två parametermarkörer nu, @1
och @2
, eftersom enkel parametrering hittade två lämpliga bokstavliga värden.
Den faktiska utförandeplanen följer mönstret i exempel 1:
Faktisk parallellparameteriserad plan
Frågetexten längst upp är nu parametriserad och egenskapsfönstret innehåller runtime parametervärden. Denna parallella plan (med en Sortera operator) parametreras definitivt av servern med enkel parameterisering .
Tillförlitliga metoder
Det finns anledningar till alla beteenden som visats hittills, och några till. Jag ska försöka förklara många av dessa i nästa del av den här serien när jag tar upp plansammanställningen.
Under tiden är situationen med showplan i allmänhet, och SSMS i synnerhet, mindre än idealisk. Det är förvirrande för människor som har arbetat med SQL Server hela sin karriär. Vilka parametermarkörer litar du på och vilka ignorerar du?
Det finns flera tillförlitliga metoder för att avgöra om ett visst påstående hade enkel parameterisering framgångsrikt tillämpat på sig eller inte.
Frågebutik
Jag börjar med en av de mest bekväma, frågebutiken. Tyvärr är det inte alltid så enkelt som du kanske föreställer dig.
Du måste aktivera frågelagringsfunktionen för databaskontexten där satsen exekveras och OPERATION_MODE
måste vara inställd på READ_WRITE
, vilket gör att frågearkivet aktivt kan samla in data.
Efter att ha uppfyllt dessa villkor innehåller utdata för showplan efter exekvering extra attribut, inklusive StatementParameterizationType . Som namnet antyder innehåller detta en kod som beskriver typen av parametrering som används för satsen.
Det är synligt i SSMS-egenskapersfönstret när rotnoden för en plan är vald:
StatementParameterizationType
Värdena dokumenteras i sys.query_store_query
:
- 0 – Ingen
- 1 – Användare (explicit parametrering)
- 2 – Enkel parametrering
- 3 – Tvångsparametrar
Detta fördelaktiga attribut visas endast i SSMS när en faktisk plan begärs och saknas när en uppskattad planen är vald. Det är viktigt att komma ihåg att planen måste vara cachelagrad . Begär en uppskattad plan från SSMS cachelagrar inte planen som skapats (sedan SQL Server 2012).
När planen är cachad visas StatementParameterizationType visas på vanliga platser, inklusive via sys.dm_exec_query_plan
.
Du kan också lita på de andra platserna som parameteriseringstypen registreras i frågearkivet, såsom query_parameterization_type_desc
kolumn i sys.query_store_query
.
En viktig varning. När frågan lagrar OPERATION_MODE
är inställd på READ_ONLY
, StatementParameterizationType attribut är fortfarande ifyllt i SSMS faktiskt planer – men det är alltid noll — vilket ger ett felaktigt intryck att uttalandet inte parametriserades när det mycket väl kunde ha varit det.
Om du är nöjd med att aktivera frågebutiken, är säker på att den är läs-skriv och bara tittar på planer efter genomförande i SSMS, kommer detta att fungera för dig.
Standardplanspredikater
Frågetexten som visas överst i det grafiska visningsfönstret i SSMS är inte tillförlitlig, som exemplen har visat. Du kan inte heller lita på ParameterList visas i Egenskaper fönster när rotnoden för planen är vald. ParameterizedText attribut som visas för uppskattad bara planer är inte heller avgörande.
Du kan dock lita på de egenskaper som är kopplade till individuella planoperatörer. De givna exemplen visar att dessa finns i verktygstipsen när du håller muspekaren över en operatör.
Ett predikat som innehåller en parametermarkör som @1
eller @2
indikerar en parametrerad plan. Operatörerna som mest sannolikt innehåller en parameter är Indexsökning , Indexsökning och Filter .
Predikat med parametermarkörer
Om numreringen börjar med @1
, den använder enkel parametrering . Tvångsparameterisering börjar med @0
. Jag bör nämna att numreringsschemat som dokumenteras här kan ändras när som helst:
Ändra varning
Ändå är det här metoden jag använder oftast för att avgöra om en plan var föremål för parametrering på serversidan. Det är i allmänhet snabbt och enkelt att kontrollera en plan visuellt för predikat som innehåller parametermarkörer. Denna metod fungerar också för båda typerna av planer, uppskattat och faktisk .
Dynamiska hanteringsobjekt
Det finns flera sätt att fråga planens cache och relaterade DMO:er för att avgöra om en sats parametriseras. Naturligtvis fungerar dessa frågor bara på planer i cachen, så uttalandet måste ha körts till slut, cachelagrats och inte av någon anledning vräkts därefter.
Det mest direkta tillvägagångssättet är att leta efter en Adhoc planera med en exakt SQL-textmatchning med intresseförklaringen. Adhoc planen kommer att vara ett skal som innehåller en ParameterizedPlanHandle om satsen parametriseras av servern. Planhandtaget används sedan för att lokalisera Förberedda planen. En Adhoc planen kommer inte att existera om optimeringen för ad hoc-arbetsbelastningar är aktiverad och uttalandet i fråga bara har körts en gång.
Den här typen av förfrågningar slutar ofta med att en betydande mängd XML strimlas och hela plancachen skannas minst en gång. Det är också lätt att få koden fel, inte minst eftersom planer i cache täcker en hel batch. En batch kan innehålla flera satser, som var och en kan parametriseras eller inte. Alla DMO:er fungerar inte med samma granularitet (batch eller uttalande) vilket gör det ganska lätt att lossna.
Ett effektivt sätt att lista intresseförklaringar, tillsammans med planfragment för just dessa individuella uttalanden, visas nedan:
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;
För att illustrera, låt oss köra en enda batch som innehåller de fyra exemplen från tidigare:
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
Utdata från DMO-frågan är:
DMO-frågeutdata
Detta bekräftar att endast exempel 1 och 4 har parametrerats framgångsrikt.
Prestandaräknare
Det är möjligt att använda SQL Statistics prestandaräknare för att få en detaljerad inblick i parameteriseringsaktivitet för båda uppskattade och faktisk planer. Räknarna som används är inte omfångade per session, så du måste använda en testinstans utan annan samtidig aktivitet för att få korrekta resultat.
Jag kommer att komplettera parameterinformationen med data från sys.dm_exec_query_optimizer_info
DMO för att tillhandahålla statistik om triviala planer också.
Viss försiktighet krävs för att förhindra att uttalanden som läser räknarinformationen modifierar dessa räknare själva. Jag ska ta itu med detta genom att skapa ett par tillfälliga lagrade procedurer:
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%';
Skriptet för att testa en viss sats ser sedan ut så här:
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;
Kommentera SHOWPLAN_XML
batchar ut för att köra målsatsen(erna) och få faktisk planer. Lämna dem på plats för uppskattning genomförandeplaner.
Att köra det hela som skrivet ger följande resultat:
Resultat från prestandaräknare
Jag har markerat ovan var värdena ändrades när jag testade exempel 3.
Ökningen av "trivial plan"-räknaren från 1050 till 1051 visar att en trivial plan hittades för testsatsen.
Räknarna för enkla parametrering ökade med 1 för både försök och misslyckanden, vilket visar att SQL Server försökte parametrisera satsen, men misslyckades.
Slutet av del 3
I nästa del av den här serien kommer jag att förklara de märkliga sakerna vi har sett genom att beskriva hur enkel parametrisering och triviala planer interagera med kompileringsprocessen.
Om du ändrade din kostnadströskel för parallellitet för att köra exemplen, kom ihåg att återställa det (min var inställd på 50):
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;