Uitvoeringsplannen
Het is ingewikkelder dan je zou verwachten aan de hand van de informatie in uitvoeringsplannen als een SQL-instructie gebruikmaakt van eenvoudige parametrering . Het is geen verrassing dat zelfs zeer ervaren SQL Server-gebruikers dit vaak bij het verkeerde eind hebben, gezien de tegenstrijdige informatie die vaak aan ons wordt verstrekt.
Laten we eens kijken naar enkele voorbeelden van het gebruik van de Stack Overflow 2010-database op SQL Server 2019 CU 14, met databasecompatibiliteit ingesteld op 150.
Om te beginnen hebben we een nieuwe niet-geclusterde index nodig:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1. Eenvoudige parametrering toegepast
Deze eerste voorbeeldquery gebruikt eenvoudige parametrering :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
De geschatte (pre-uitvoerings)plan heeft de volgende parametriseringsgerelateerde elementen:
Geschatte eigenschappen voor planparametrering
Let op de @1
parameter wordt overal geïntroduceerd, behalve de zoektekst die bovenaan wordt weergegeven.
De echte (post-uitvoering) plan heeft:
Eigenschappen voor de daadwerkelijke parametrering van het plan
Merk op dat het eigenschappenvenster nu de ParameterizedText
heeft verloren element, terwijl informatie wordt verkregen over de runtime-waarde van de parameter. De geparameteriseerde zoektekst wordt nu bovenaan het venster weergegeven met '@1
’ in plaats van ‘999’.
2. Eenvoudige parametrering niet toegepast
Dit tweede voorbeeld doet niet gebruik eenvoudige parametrering:
-- Projecting an extra column SELECT U.DisplayName, U.CreationDate -- NEW FROM dbo.Users AS U WHERE U.Reputation = 999;
De geschatte plan toont:
Geschat plan zonder parameters
Deze keer is de parameter @1
ontbreekt in de Index Seek tooltip, maar de geparametriseerde tekst en andere parameterlijstelementen zijn hetzelfde als voorheen.
Laten we eens kijken naar de werkelijke uitvoeringsplan:
Eigenlijk niet-geparametriseerd plan
De resultaten zijn hetzelfde als de vorige geparametriseerde werkelijke plan, behalve nu de Index Seek tooltip geeft de niet-geparametreerde waarde '999' weer. De zoektekst die bovenaan wordt weergegeven, gebruikt de @1
parametermarkering. Het eigenschappenvenster gebruikt ook @1
en geeft de runtime-waarde van de parameter weer.
De query is geen geparametriseerde instructie ondanks al het bewijs van het tegendeel.
3. Parametrering mislukt
Mijn derde voorbeeld is ook niet geparametreerd door de server:
-- LOWER function used SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999;
De geschatte plan is:
Geschatte parametrering van plan mislukt
Er is geen sprake van een @1
parameter nu overal, en de Parameterlijst gedeelte van het eigenschappenvenster ontbreekt.
De echte uitvoeringsplan is hetzelfde, dus ik zal niet de moeite nemen om het te laten zien.
4. Parallel geparametriseerd plan
Ik wil je nog een voorbeeld laten zien van parallellisme in het uitvoeringsplan. De lage geschatte kosten van mijn testquery's betekenen dat we de kostendrempel voor parallellisme moeten verlagen tot 1:
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 1; RECONFIGURE;
Het voorbeeld is deze keer wat complexer:
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC;
De geschatte uitvoeringsplan is:
Geschat parallel geparametriseerd plan
De querytekst bovenaan blijft ongeparametreerd, terwijl al het andere dat wel is. Er zijn nu twee parametermarkeringen, @1
en @2
, omdat eenvoudige parametrering vond twee geschikte letterlijke waarden.
De echte uitvoeringsplan volgt het patroon van voorbeeld 1:
Eigenlijk parallel geparametriseerd plan
De querytekst bovenaan is nu geparametriseerd en het eigenschappenvenster bevat runtime-parameterwaarden. Dit parallelle plan (met a Sorteren operator) is zeker geparametreerd door de server met behulp van eenvoudige parametrering .
Betrouwbare methoden
Er zijn redenen voor al het gedrag dat tot nu toe is getoond, en nog een paar meer. Ik zal proberen veel hiervan uit te leggen in het volgende deel van deze serie wanneer ik plancompilatie behandel.
Ondertussen is de situatie met showplan in het algemeen en SSMS in het bijzonder niet ideaal. Het is verwarrend voor mensen die hun hele loopbaan met SQL Server hebben gewerkt. Welke parametermarkeringen vertrouw je en welke negeer je?
Er zijn verschillende betrouwbare methoden om te bepalen of op een bepaalde instructie eenvoudige parametrisering is toegepast of niet.
Query-winkel
Ik zal beginnen met een van de handigste, de query store. Helaas is het niet altijd zo eenvoudig als je zou denken.
U moet de functie voor het opslaan van query's inschakelen voor de databasecontext waar de instructie wordt uitgevoerd en de OPERATION_MODE
moet zijn ingesteld op READ_WRITE
, waardoor de query store actief gegevens kan verzamelen.
Nadat aan deze voorwaarden is voldaan, bevat de showplan-uitvoer na uitvoering extra attributen, waaronder het StatementParameterizationType . Zoals de naam al doet vermoeden, bevat dit een code die het type parametrering beschrijft dat voor de instructie wordt gebruikt.
Het is zichtbaar in het SSMS-eigenschappenvenster wanneer het hoofdknooppunt van een plan is geselecteerd:
StatementParameterizationType
De waarden zijn gedocumenteerd in sys.query_store_query
:
- 0 – Geen
- 1 – Gebruiker (expliciete parametrering)
- 2 – Eenvoudige parametrering
- 3 – Geforceerde parametrering
Dit voordelige kenmerk verschijnt alleen in SSMS wanneer een echte plan is aangevraagd en ontbreekt wanneer een geschatte plan is gekozen. Het is belangrijk om te onthouden dat het plan gecached moet zijn . Een geschatte . aanvragen plan van SSMS slaat het geproduceerde plan niet op in de cache (sinds SQL Server 2012).
Zodra het plan in de cache is opgeslagen, wordt het StatementParameterizationType verschijnt op de gebruikelijke plaatsen, waaronder via sys.dm_exec_query_plan
.
U kunt ook vertrouwen op de andere plaatsen waar het type parametrering is vastgelegd in het queryarchief, zoals de query_parameterization_type_desc
kolom in sys.query_store_query
.
Een belangrijk voorbehoud. Wanneer de query OPERATION_MODE
opslaat is ingesteld op READ_ONLY
, het StatementParameterizationType attribuut is nog steeds ingevuld in SSMS actual plannen, maar het is altijd nul — een verkeerde indruk wekken dat de verklaring niet geparametriseerd was, terwijl dat wel zo zou kunnen zijn.
Als u tevreden bent met het inschakelen van het opslaan van query's, er zeker van bent dat het lezen-schrijven is en alleen naar plannen na de uitvoering in SSMS kijkt, zal dit voor u werken.
Standard Plan Predicates
De vraagtekst die bovenaan het grafische showplan-venster in SSMS wordt weergegeven, is niet betrouwbaar, zoals de voorbeelden hebben aangetoond. U kunt ook niet vertrouwen op de ParameterList weergegeven in de Eigenschappen venster wanneer het hoofdknooppunt van het plan is geselecteerd. De Geparameteriseerde Tekst kenmerk getoond voor geschatte alleen plannen is ook niet overtuigend.
U kunt echter vertrouwen op de eigenschappen die aan afzonderlijke planoperators zijn gekoppeld. De gegeven voorbeelden laten zien dat deze aanwezig zijn in de tooltips wanneer u over een operator zweeft.
Een predikaat met een parametermarkering zoals @1
of @2
geeft een geparametriseerd plan aan. De operators die het meest waarschijnlijk een parameter bevatten, zijn Index Scan , Index zoeken , en Filteren .
Predikaten met parametermarkeringen
Als de nummering begint met @1
, het gebruikt eenvoudige parametrering . Geforceerde parametrering begint met @0
. Ik moet vermelden dat het hier gedocumenteerde nummeringsschema op elk moment kan worden gewijzigd:
Waarschuwing wijzigen
Niettemin, dit is de methode die ik gebruik meestal om te bepalen of een plan onderhevig was aan parameterisatie aan de serverzijde. Het is over het algemeen snel en eenvoudig om een plan visueel te controleren op predikaten die parametermarkeringen bevatten. Deze methode werkt ook voor beide soorten plannen, geschat en werkelijk .
Dynamische beheerobjecten
Er zijn verschillende manieren om de plancache en gerelateerde DMO's te doorzoeken om te bepalen of een instructie is geparametriseerd. Uiteraard werken deze zoekopdrachten alleen voor plannen in de cache, dus de instructie moet volledig zijn uitgevoerd, in de cache zijn opgeslagen en vervolgens om welke reden dan ook niet zijn verwijderd.
De meest directe benadering is om te zoeken naar een Adhoc plan met behulp van een exacte SQL-tekst die overeenkomt met de verklaring van interesse. De Adhoc plan is een shell met een ParameterizedPlanHandle als de instructie is geparametriseerd door de server. De planhandle wordt vervolgens gebruikt om de Prepared . te lokaliseren plan. Een Adhoc plan bestaat niet als de optimalisatie voor ad-hocworkloads is ingeschakeld en de betreffende instructie slechts één keer is uitgevoerd.
Dit type onderzoek leidt er vaak toe dat een aanzienlijke hoeveelheid XML wordt versnipperd en de volledige plancache minstens één keer wordt gescand. Het is ook gemakkelijk om de code verkeerd te krijgen, niet in de laatste plaats omdat plannen in de cache een hele batch beslaan. Een batch kan meerdere statements bevatten, die elk al dan niet geparametriseerd zijn. Niet alle DMO's werken met dezelfde granulariteit (batch of verklaring), waardoor het vrij eenvoudig is om los te komen.
Hieronder vindt u een efficiënte manier om interessante stellingen op te sommen, samen met planfragmenten voor alleen die individuele stellingen:
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;
Laten we ter illustratie een enkele batch draaien met de vier voorbeelden van eerder:
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
De output van de DMO-query is:
DMO-query-uitvoer
Dit bevestigt dat alleen voorbeelden 1 en 4 met succes zijn geparametriseerd.
Prestatietellers
Het is mogelijk om de prestatiemeteritems van SQL Statistics te gebruiken om een gedetailleerd inzicht te krijgen in de parametreeractiviteit voor beide geschatte en werkelijk plannen. De gebruikte tellers hebben geen bereik per sessie, dus u moet een testinstantie gebruiken zonder andere gelijktijdige activiteit om nauwkeurige resultaten te krijgen.
Ik ga de parameterisatietellerinformatie aanvullen met gegevens uit de sys.dm_exec_query_optimizer_info
DMO biedt ook statistieken over triviale plannen.
Enige voorzichtigheid is geboden om te voorkomen dat verklaringen die de tellerinformatie lezen, die tellers zelf wijzigen. Ik ga dit oplossen door een aantal tijdelijke opgeslagen procedures te maken:
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%';
Het script om een bepaalde instructie te testen ziet er dan als volgt uit:
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;
Reageer op de SHOWPLAN_XML
batches uit om de doelverklaring(en) uit te voeren en werkelijk . te krijgen plannen. Laat ze op hun plaats voor geschatte uitvoeringsplannen.
Het geheel uitvoeren zoals geschreven geeft de volgende resultaten:
Prestatieteller-testresultaten
Ik heb hierboven aangegeven waar de waarden veranderden bij het testen van voorbeeld 3.
De stijging van de teller 'triviaal plan' van 1050 naar 1051 laat zien dat er een triviaal plan is gevonden voor de testverklaring.
De tellers voor eenvoudige parametrering zijn met 1 verhoogd voor zowel pogingen als mislukkingen, wat aantoont dat SQL Server probeerde de instructie te parametriseren, maar faalde.
Einde van deel 3
In het volgende deel van deze serie zal ik de merkwaardige dingen die we hebben gezien uitleggen door te beschrijven hoe eenvoudige parametrering en triviale plannen interactie met het compilatieproces.
Als u uw kostendrempel voor parallellisme heeft gewijzigd om de voorbeelden uit te voeren, vergeet niet om het te resetten (de mijne was ingesteld op 50):
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;