sql >> Database teknologi >  >> RDS >> Sqlserver

SQL Server Internals:Plan Caching Pt. I – Genbrug af planer

SQL Server har eksisteret i over 30 år, og jeg har arbejdet med SQL Server i næsten lige så lang tid. Jeg har set en masse ændringer gennem årene (og årtier!) og versioner af dette utrolige produkt. I disse indlæg vil jeg dele med dig, hvordan jeg ser på nogle af funktionerne eller aspekterne af SQL Server, nogle gange sammen med en smule historisk perspektiv.

Tjek Kalens seneste blogs om problematiske operatører her.

Planer for SQL-serverdiagnostik kan være dyre at lave, da forespørgselsoptimeringsværktøjet skal kunne finde en god plan for enhver juridisk forespørgsel, der sendes. Optimizeren evaluerer flere joinordre, flere indekser. og forskellige typer join- og grupperingsalgoritmer, afhængigt af din forespørgsel og de involverede tabeller. Hvis den samme forespørgsel køres igen, kan SQL Server spare mange ressourcer ved at genbruge en eksisterende plan. Men det er ikke altid muligt at genbruge en eksisterende plan, og det er ikke altid en god ting at gøre det. I de næste to artikler vil vi se på, hvornår en plan genbruges, og hvornår den er genkompileret.

Først vil vi se på de forskellige varianter af planer og den metadatavisning, som jeg oftest bruger til at se på, hvad der er i min plancache. Jeg har skrevet et eget synspunkt, der giver den information, som jeg oftest finder mest brugbar. SQL Server cacherer seks forskellige typer forespørgselsplaner, men kun to bruges normalt til plancachetuning. Disse er COMPILED PLAN og COMPILED PLAN STUB. Min visning filtrerer alle undtagen disse to typer cache-objekter fra. KOMPILEREDE PLANER kommer i tre varianter:AD HOC, PREPARED og PROC. Jeg vil kommentere alle tre slags.

Selvom vi bare kigger på KOMPILEREDE PLANER, er der stadig mange planer i cachen, som normalt skal ignoreres, da de er genereret af SQL Server selv. Disse omfatter planer, der leder efter filstream- eller fuldtekstsøgeindekser eller interne forespørgsler, der arbejder med In-memory OLTP. Så min visning tilføjer filtre for at prøve at vænne de fleste af de planer, jeg ikke er interesseret i. Du kan downloade et script til at bygge denne visning, kaldet sp_cacheobjects , fra her.

Selv med alle de filtre, min visning bruger, er der stadig nogle af SQL Servers egne interne forespørgsler i cachen; Jeg rydder normalt planens cache ud ofte, når jeg tester i dette område. Den enkleste måde at rydde ALLE planer fra cachen er med kommandoen:DBCC FREEPROCCACHE.

Adhoc-kompilerede planer

Den enkleste type plan er Adhoc. Dette bruges til grundlæggende forespørgsler, der ikke passer ind i en anden kategori. Hvis du har downloadet mit script og oprettet min sp_cacheobjects-visning, kan du køre følgende. Enhver version af AdventureWorks-databasen burde fungere. Dette script laver en kopi af en tabel og bygger et par unikke indekser på den. Den masserer også SubTotal-beløbet for at fjerne eventuelle decimaler.

USE AdventureWorks2016;
GO
DROP TABLE IF EXISTS newsales;
GO
-- Make a copy of the Sales.SalesOrderHeader table
SELECT * INTO dbo.newsales
FROM Sales.SalesOrderHeader;
GO
UPDATE dbo.newsales
SET SubTotal = cast(cast(SubTotal as int) as money);
GO
CREATE UNIQUE index newsales_ident
    ON newsales(SalesOrderID);
GO
CREATE INDEX IX_Sales_SubTotal ON newsales(SubTotal);
GO
-- Adhoc query plan reuse
DBCC FREEPROCCACHE;
GO
-- adhoc query
SELECT * FROM dbo.newsales
WHERE SubTotal = 4;
GO
SELECT * FROM sp_cacheobjects;
GO

I mit output ser du to Adhoc-planer. Den ene er for SELECT-erklæringen fra nyhedsudsalget tabel, og den anden er til SELECT fra mine sp_cacheobjects udsigt. Fordi planen er cachelagret, hvis den PRÆCIS samme forespørgsel køres igen, kan den samme plan genbruges, og du vil se brugstællingerne værdistigning. Der er dog en fangst. For at en Adhoc-plan kan genbruges, skal SQL-strengen være helt nøjagtig den samme. Hvis du ændrer tegn i SQL, genkendes forespørgslen ikke som den samme forespørgsel, og en ny plan genereres. Hvis jeg endda tilføjer et mellemrum, inkluderer kommentaren eller et nyt linjeskift, er det ikke den samme streng. Hvis jeg ændrer store og små bogstaver, betyder det, at der er forskellige ASCII-kodeværdier, og dermed ikke den samme streng.

Du kan prøve dette selv ved at køre forskellige varianter af min første SELECT-erklæring fra nyhedssalget bord. Du vil se en anden række i cachen for hver enkelt. Efter at jeg kørte adskillige variationer – ændrede det nummer, jeg søgte efter, ændrede sagen, tilføjede kommentaren og en ny linje, ser jeg følgende i cachen. SELECT fra min visning bliver genbrugt, men alt andet har et usecounts værdi på 1.

Et yderligere krav for genbrug af Adhoc-forespørgselsplanen er, at den session, der kører forespørgslen, skal have de samme SET-indstillinger i kraft . Der er en anden kolonne i outputtet, du kan se til højre for forespørgselsteksten, kaldet SETOPTS. Dette er en bitstreng med en bit for hver relevant SET-indstilling. Hvis du ændrer en af ​​mulighederne, f.eks. SÆT ANSI_NULLS FRA, vil bitstrengen ændre sig, og den samme plan med den originale bitstreng kan ikke genbruges.

Forberedte kompilerede planer

Den anden type cachelagrede kompilerede plan er en FORBEREDT plan. Hvis din forespørgsel opfylder et bestemt sæt krav. Det kan faktisk parametreres automatisk. Det vises i metadataene som FORBEREDT, og SQL-strengen viser en parametermarkør. Her er et eksempel:

PREPARED-planen viser parametermarkøren som @1 og inkluderer ikke den aktuelle værdi. Bemærk, at der er en række til en ADHOC-forespørgsel med en faktisk værdi på 5555, men det er faktisk kun en "skal" af den rigtige forespørgsel. Den cacherer ikke hele planen, men kun forespørgslen og nogle få identificerende detaljer, for at hjælpe forespørgselsprocessoren med at finde den parametriserede plan i cachen. Bemærk størrelsen (sidebrugt ) er meget mindre end den UDARBEJDEDE plan.

Standardparametreringstilstanden, kaldet SIMPLE parameterisering, er ekstremt streng med hensyn til, hvilke planer der kan parametreres. Det er i virkeligheden kun den enkleste forespørgsel, der kan parametreres som standard. Forespørgsler, der indeholder JOIN, GROUP BY, OR og mange andre relativt almindelige forespørgselskonstruktioner, forhindrer en forespørgsel i at blive parameteriseret. Ud over ikke at have nogen af ​​disse konstruktioner, er det vigtigste for SIMPLE parameterisering, at forespørgslen er SIKKER. Det betyder, at der kun er én mulig plan, uanset hvilke værdier der sendes for nogen parametre. (Selvfølgelig kan en forespørgsel uden nogen parametre også være SIKKER.) Min forespørgsel leder efter et nøjagtigt match i kolonnen SalesOrderID , som har et unikt indeks på sig. Så det eksisterende ikke-klyngede indeks kunne bruges til at finde en hvilken som helst matchende række. Uanset hvilken værdi jeg bruger, 55555 eller noget andet, vil der aldrig være mere end én række, hvilket betyder, at planen stadig vil være god.

I mit eksempel på min Adhoc-forespørgselsplan ledte jeg efter matchende værdier for SubTotal . Nogle Subtotal værdier forekommer et par gange eller slet ikke, så et ikke-klynget indeks ville være godt. Men andre værdier kan forekomme mange gange, så indekset ville IKKE være nyttigt. Forespørgselsplanen er således ikke SIKKER, og forespørgslen kan ikke parametreres. Derfor så vi en Adhoc-plan for mit første eksempel.

HVIS du har forespørgsler med JOIN eller andre ikke-tilladte konstruktioner, kan du fortælle SQL Server om at være mere aggressiv i parametrering ved at ændre en databaseindstilling:

ALTER DATABASE AdventureWorks2016 SET parameterization FORCED;
GO

Indstilling af din database til FORCERT parameterisering betyder, at SQL Server vil parametrere en hel del flere forespørgsler, inklusive dem med JOIN, GROUP BY, OR osv. Men det betyder også, at SQL Server kan parameterisere en forespørgsel, der ikke er SIKKER. Det kan komme med en plan, der er god, når kun få rækker returneres, og derefter genbruge planen, når mange rækker returneres. Dette kan ende med en meget suboptimal ydeevne.

En sidste mulighed for en udarbejdet plan er, når du eksplicit udarbejder en plan. Denne adfærd påkaldes normalt gennem et program med SQLPrepare og SQLEexe API'er. Du angiver, hvad forespørgslen er med parametermarkeringer, du angiver datatyperne, og du angiver de specifikke værdier, der skal bruges. Den samme forespørgsel kan derefter køres igen med forskellige specifikke værdier, og den eksisterende plan vil blive brugt. Selvom det kan være muligt at bruge eksplicit forberedte planer i de tilfælde, hvor SQL Server ikke parametrerer, og du ønsker det, forhindrer det ikke SQL Server i at bruge en plan, der IKKE er god til efterfølgende parametre. Du skal teste dine forespørgsler med mange forskellige inputværdier og sikre dig, at du får den forventede ydeevne, hvis og når en plan genbruges.

Metadataene (f.eks. mine sp_cacheobjects visning) viser bare FORBEREDT for alle tre typer planer:TVUNGET og ENKEL autoparameterisering og EKSPLITIGT parameterisering.

Proc-kompilerede planer

Den sidste objtype værdi for kompilerede planer er for en lagret procedure, som vises som Proc. Når det er muligt, er lagrede procedurer det bedste valg til genanvendelig kode på grund af deres lette styring fra selve serveren, men det betyder ikke, at de altid er garanteret at give den bedste ydeevne. Ligesom ved brug af FORCED-parametriseringsmuligheden (og også den eksplicitte parameterisering), bruger lagrede procedurer 'parametersniffing'. Det betyder, at den første parameterværdi, der sendes ind, bestemmer planen. Hvis efterfølgende eksekveringer fungerer fint med den samme plan, så er parametersniffing ikke et problem og kan faktisk være fordelagtigt, fordi det sparer os for omkostningerne ved at omkompilere og genoptimere. Men hvis efterfølgende henrettelser med forskellige værdier ikke skulle bruge den oprindelige plan, så har vi et problem. Jeg viser dig et eksempel på parametersniffing, der forårsager et problem

Jeg opretter en lagret procedure baseret på nyhedssalget tabel vi brugte tidligere. Proceduren vil have en enkelt forespørgsel, der filtrerer baseret på SalesOrderID kolonne, hvorpå vi byggede et ikke-klynget indeks. Forespørgslen vil være baseret på en ulighed, så for nogle værdier kan forespørgslen kun returnere nogle få rækker og bruge indekset, og for andre værdier kan forespørgslen returnere MANGE rækker. Med andre ord er forespørgslen ikke SIKKER.

USE AdventureWorks2016;
GO
DROP PROC IF EXISTS get_sales_range;
GO
CREATE PROC get_sales_range
   @num int
AS
    SELECT * FROM dbo.newsales
    WHERE SalesOrderID < @num;
GO

Jeg vil bruge indstillingen SET STATISTICS IO ON for at se, hvor meget arbejde der bliver gjort, når proceduren udføres. Først vil jeg udføre det med en parameter, der kun returnerer nogle få rækker:

SET STATISTICS IO ON
GO
EXEC get_sales_range 43700;
GO

STATISTICS IO-værdien rapporterer, at det tog 43 logiske læsninger at returnere 41 rækker. Dette er normalt for et ikke-klynget indeks. Nu udfører vi proceduren igen med en meget større værdi.

EXEC get_sales_range 66666;
GO
SELECT * FROM sp_cacheobjects;
GO
This time, we see that SQL Server used a whole lot more reads:

Faktisk en tabelscanning på newsales tabel tager kun 843 læsninger, så dette er langt dårligere ydeevne end en tabelscanning. sp_cacheobjects visningen viser os, at PROC-planen er blevet genbrugt til denne anden udførelse. Dette er et eksempel på, hvor parametersniffing IKKE er en god ting.

Så hvad kan vi gøre, når parametersniffing er et problem? I det næste indlæg fortæller jeg dig, hvornår SQL Server kommer med en ny plan og ikke genbruger gamle. Vi vil se på, hvordan du kan fremtvinge (eller tilskynde til) rekompilering, og vi vil også se, hvornår SQL Server automatisk genkompilerer dine forespørgsler.

Spotlight Cloud kan revolutionere din præstationsovervågning og SQL-serverdiagnostik. Kom godt i gang med din gratis prøveperiode ved at bruge linket nedenfor:


  1. Størrelsesgrænse for JSON-datatype i PostgreSQL

  2. SQLite JSON_OBJECT()

  3. Hvordan viser jeg kørende processer i Oracle DB?

  4. Sådan fungerer TIMESTAMPDIFF() i MariaDB