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

Opfølgning på Summer Performance Palooza 2013

Den 27. juni afholdt PASS Performance Virtual Chapter sin 2013 Summer Performance Palooza - en slags nedskaleret 24 Hours of PASS, men fokuserede udelukkende på præstationsrelaterede emner. Jeg holdt en session med titlen "10 dårlige vaner, der kan dræbe ydeevne", hvor jeg behandlede følgende 10 begreber:

  1. VÆLG *
  2. Blindindekser
  3. Intet skemapræfiks
  4. Standardmarkørindstillinger
  5. sp_ præfiks
  6. Tillader cache-bloat
  7. Vide datatyper
  8. SQL-serverstandarder
  9. Overbrug af funktioner
  10. "Virker på min maskine"

Du husker måske nogle af disse emner fra sådanne præsentationer som min "Dårlige vaner og bedste praksis"-foredrag eller vores ugentlige Query Tuning-webinarer, som jeg har været vært for med Kevin Kline fra begyndelsen af ​​juni til denne uge. (De 6 videoer vil i øvrigt være tilgængelige i begyndelsen af ​​august på YouTube.)

Min session havde 351 deltagere, og jeg fik god feedback. Jeg ville tage fat på noget af det.

Først et konfigurationsproblem:Jeg brugte en helt ny mikrofon og anede ikke, at hvert tastetryk ville lyde som torden. Jeg har løst det problem med en bedre placering af mine perifere enheder, men jeg vil gerne undskylde til alle, der er berørt af det.

Dernæst downloads; dækket og prøverne sendes til begivenhedsstedet. De er nederst på siden, men du kan også downloade dem lige her.

Til sidst er det følgende en liste over spørgsmål, der blev stillet under sessionen, og jeg ville være sikker på, at jeg tog fat på dem, der ikke blev besvaret under live Q &A. Jeg undskylder, at jeg har presset dette ind på knap en måned , men der var en masse spørgsmål, og jeg ønskede ikke at offentliggøre dem i dele.

Sp:Hvis du har en proc, der kan have vildt varierende inputværdier for de givne parametre, og resultatet er, at den cachelagrede plan ikke er optimal i de fleste tilfælde, er det bedst at oprette proc'en MED RECOMPILE og tage den lille ydeevne ramt hver gang den kører?

A: Du bliver nødt til at gribe dette an fra sag til sag, da det virkelig vil afhænge af en række forskellige faktorer (herunder planens kompleksitet). Bemærk også, at du kan lave omkompilering på sætningsniveau, således at kun de berørte sætninger skal tage hit, i modsætning til hele modulet. Paul White mindede mig om, at folk ofte 'retter' parametersniffing med RECOMPILE , men alt for ofte betyder det 2000-stil WITH RECOMPILE snarere end den meget bedre OPTION (RECOMPILE) , som ikke kun begrænser sig til sætningen, men også muliggør parameterindlejring, som WITH RECOMPILE gør ikke. Så hvis du vil bruge RECOMPILE for at forhindre parametersniffing, føj det til sætningen, ikke modulet.

Sp:Hvis du bruger option recompile på dynamisk sql, vil du se et stort præstationshit

A: Som ovenfor vil dette afhænge af omkostningerne og kompleksiteten af ​​planerne, og der er ingen måde at sige, "Ja, der vil altid være et stort præstationshit." Du skal også være sikker på at sammenligne det med alternativet.

Sp:Hvis der er et klynget indeks på indsætdato, bruger vi senere, når vi henter data, konverteringsfunktion, hvis vi bruger direkte sammenligning, er forespørgselsdatoen ikke læsbar, i den virkelige verden, hvad er det bedre valg

A: Jeg er ikke sikker på, hvad "læsbar i den virkelige verden" betyder. Hvis du mener, at du vil have output i et bestemt format, er du normalt bedre stillet til at konvertere til en streng på klientsiden. C# og de fleste af de andre sprog, du sandsynligvis bruger på præsentationsniveauet, er mere end i stand til at formatere dato/klokkeslæt output fra databasen i det regionale format, du ønsker.

Sp:Hvordan bestemmer man antallet af gange, en cachelagret plan bruges – er der en kolonne med den værdi eller nogen forespørgsler på internettet, der vil give denne værdi? Endelig, ville sådanne tællinger kun være relevante siden sidste genstart?

A: De fleste DMV'er er kun gyldige siden sidste servicestart, og selv andre kan skylles hyppigere (selv efter behov – både utilsigtet og med vilje). Plancachen er selvfølgelig i konstant forandring, og AFAIK-planer, der falder ud af cachen, bevarer ikke deres tidligere optælling, hvis de dukker ind igen. Så selv når du ser en plan i cachen, er jeg ikke 100% sikker på, at du kan tro på det antal brug, du finder.

Når det er sagt, er det, du sandsynligvis leder efter, sys.dm_exec_cached_plans.usecounts og du kan også finde sys.dm_exec_procedure_stats.execution_count for at hjælpe med at supplere information til procedurer, hvor individuelle udsagn inden for procedurerne ikke findes i cachen.

Sp:Hvad er problemerne, når du opgraderer db-motoren til en ny version, men efterlader brugerdatabaserne i ældre kompatibilitetstilstande?

A: De største bekymringer omkring dette er evnen til at bruge bestemt syntaks, såsom OUTER APPLY eller variabler med en funktion med tabelværdi. Jeg er ikke bekendt med nogen tilfælde, hvor brug af en lavere kompatibilitet har nogen direkte indvirkning på ydeevnen, men et par ting, der typisk anbefales, er at genopbygge indekser og opdatere statistik (og at få din leverandør til at understøtte det nyere kompatibilitetsniveau ASAP). Jeg har set det løse uventet ydeevneforringelse i et betydeligt antal tilfælde, men jeg har også hørt nogle meninger om, at det ikke er nødvendigt og måske endda uklogt.

Sp:På * betyder det noget, når man laver en exists-sætning

A: Nej, i det mindste med hensyn til ydeevne, en undtagelse hvor SELECT * det er ligegyldigt, når det bruges i en EXISTS klausul. Men hvorfor ville du bruge * her? Jeg foretrækker at bruge EXISTS (SELECT 1 ...). – optimeringsværktøjet vil behandle dem på samme måde, men på en måde selvdokumenterer den koden og sikrer, at læserne forstår, at underforespørgslen ikke returnerer nogen data (selvom de går glip af den store EXISTS uden for). Nogle mennesker bruger NULL , og jeg aner ikke hvorfor jeg begyndte at bruge 1, men jeg finder NULL også lidt uintuitivt.

*Bemærk* du skal være forsigtig, hvis du prøver at bruge EXISTS (SELECT *) inde i et modul, der er skemabundet:

CREATE VIEW dbo.ThisWillNotWork
WITH SCHEMABINDING
AS
  SELECT BusinessEntityID
    FROM Person.Person AS p
	WHERE EXISTS (SELECT * FROM Sales.SalesOrderHeader AS h
	  WHERE h.SalesPersonID = p.BusinessEntityID);

Du får denne fejl:

Msg 1054, Level 15, State 6, Procedure ThisWillNotWork, Linje 6
Syntaks '*' er ikke tilladt i skemabundne objekter.

Men ved at ændre det til SELECT 1 fungerer fint. Så måske er det endnu et argument for at undgå SELECT * selv i det scenarie.

Sp:Er der et ressourcelink til de bedste kodningsstandarder?

A: Der er sandsynligvis hundredvis på tværs af en række forskellige sprog. Ligesom navnekonventioner er kodningsstandarder en meget subjektiv ting. Det er lige meget, hvilken konvention du beslutter, der fungerer bedst for dig; hvis du kan lide tbl præfikser, gå amok! Foretrækker Pascal frem for bigEndian, tag det. Ønsker at præfikse dine kolonnenavne med datatypen, såsom intCustomerID , jeg vil ikke stoppe dig. Det vigtigere er, at du definerer en konvention og anvender den *konsekvent.*

Når det er sagt, hvis du vil have mine meninger, så mangler jeg dem ikke.

Sp:Er XACT_ABORT noget, der kan bruges i SQL Server 2008 og fremefter?

A: Jeg kender ikke til nogen planer om at udfase XACT_ABORT så det burde fortsætte med at fungere fint. Helt ærligt ser jeg ikke dette brugt så ofte nu, hvor vi har TRY / CATCH (og THROW fra SQL Server 2012).

Sp:Hvordan kan en inline tabelfunktion på et kryds anvendes sammenlignet med den skalarfunktion, der blev kaldt 1.000x?

A: Jeg testede ikke dette, men i mange tilfælde kan det have stor indflydelse på ydeevnen at erstatte en skalarfunktion med en inline-tabel-vurderet funktion. Problemet, jeg finder, er, at det kan være en betydelig mængde arbejde på systemet, der blev skrevet før APPLY at lave denne switch. eksisteret eller stadig administreres af folk, der ikke har taget denne bedre tilgang til sig.

Sp:Jeg har en forespørgsel, der kører rigtig langsomt første gang (~1 min) og hurtigt (~3 sekunder) hver anden gang. Hvor skal jeg begynde at se på, hvor præstationsproblemet kommer fra første gang?

A: To ting kommer til at tænke på:(1) forsinkelsen har at gøre med kompileringstiden eller (2) forsinkelsen har at gøre med mængden af ​​data, der indlæses for at tilfredsstille forespørgslen, og første gang det skal komme fra disken og ikke hukommelse. For (1) kan du udføre forespørgslen i SQL Sentry Plan Explorer, og statuslinjen vil vise dig kompileringstid for de første og efterfølgende påkaldelser (selvom et minut virker ret overdrevent for dette og usandsynligt). Hvis du ikke finder nogen forskel, er det måske bare systemets natur:utilstrækkelig hukommelse til at understøtte mængden af ​​data, du forsøger at indlæse med denne forespørgsel i kombination med andre data, der allerede var i bufferpuljen. Hvis du ikke tror på, at nogen af ​​disse er problemet, så se, om de to forskellige henrettelser rent faktisk giver forskellige planer – hvis der er forskelle, så send planerne til answers.sqlperformance.com, og vi vil med glæde tage et kig . Faktisk kan indfangning af faktiske planer for begge eksekveringer ved hjælp af Plan Explorer under alle omstændigheder også fortælle dig om eventuelle forskelle i I/O og kan føre til, hvor SQL Server bruger sin tid på den første, langsommere kørsel.

Sp.:Jeg får parametersniffing ved hjælp af sp_executesql, ville Optimize til ad hoc-arbejdsbelastninger løse dette, da kun planstubben er i cachen?

A: Nej, jeg tror ikke, at indstillingen Optimer til ad hoc-arbejdsbelastninger vil hjælpe dette scenarie, da parametersniffing indebærer, at efterfølgende eksekveringer af den samme plan bruges til forskellige parametre og med væsentligt forskellig præstationsadfærd. Optimer til ad hoc-arbejdsbelastninger bruges til at minimere den drastiske indvirkning på planens cache, der kan ske, når du har et stort antal forskellige SQL-sætninger. Så medmindre du taler om indvirkning på plan-cachen af ​​mange forskellige udsagn, du sender til sp_executesql – som ikke ville blive karakteriseret som parametersniffing – jeg tror, ​​at eksperimentere med OPTION (RECOMPILE) kan have et bedre resultat, eller hvis du kender de parameterværdier, der *gør* giver gode resultater på tværs af en række parameterkombinationer, skal du bruge OPTIMIZE FOR . Dette svar fra Paul White kan give meget bedre indsigt.

Sp:Er der en måde at køre dynamisk SQL og IKKE gemme forespørgselsplanen?

A: Selvfølgelig, inkluder bare OPTION (RECOMPILE) i den dynamiske SQL-tekst:

DBCC FREEPROCCACHE;
 
USE AdventureWorks2012;
GO
SET NOCOUNT ON;
GO
 
EXEC sp_executesql 
  N'SELECT TOP (1) * INTO #x FROM Sales.SalesOrderHeader;';
GO
EXEC sp_executesql 
  N'SELECT TOP (1) * INTO #x FROM Sales.SalesOrderDetail OPTION (RECOMPILE);'
GO
 
SELECT t.[text], p.usecounts
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.[plan_handle]) AS t
WHERE t.[text] LIKE N'%Sales.' + 'SalesOrder%';

Resultater:1 række, der viser Sales.SalesOrderHeader forespørgsel.

Nu, hvis en sætning i batchen IKKE inkluderer OPTION (RECOMPILE) , planen kan stadig være cachelagret, den kan bare ikke genbruges.

Sp:Kan du bruge BETWEEN på datoeksemplet fra #9 i stedet hvis>=og

A: Nå, BETWEEN er ikke semantisk ækvivalent med >= AND < , men snarere >= AND <= , og optimerer og udfører på nøjagtig samme måde. Under alle omstændigheder bruger jeg med vilje ikke BETWEEN på datointervalforespørgsler – nogensinde – fordi der ikke er nogen måde at gøre det til et åbent interval. Med BETWEEN , begge ender er inklusive, og dette kan være meget problematisk afhængigt af den underliggende datatype (nu eller på grund af en fremtidig ændring, du måske ikke kender til). Titlen kan virke lidt hård, men jeg går meget i detaljer om dette i følgende blogindlæg:

Hvad har BETWEEN og djævelen til fælles?

Sp.:Hvad gør "local fast_forward" egentlig i en markør?

A: FAST_FORWARD er faktisk den korte form af READ_ONLY og FORWARD_ONLY . Her er, hvad de gør:

  • LOCAL gør det således, at ydre scopes (som standard er en markør GLOBAL). medmindre du har ændret indstillingen på instansniveau).
  • READ_ONLY gør det sådan, at du ikke kan opdatere markøren direkte, f.eks. ved hjælp af WHERE CURRENT OF .
  • FORWARD_ONLY forhindrer muligheden for at scrolle, f.eks. ved hjælp af FETCH PRIOR eller FETCH ABSOLUTE i stedet for FETCH NEXT .

At indstille disse muligheder, som jeg demonstrerede (og har blogget om), kan have en betydelig indflydelse på ydeevnen. Meget sjældent ser jeg markører i produktionen, der faktisk skal afvige fra dette sæt funktioner, men normalt er de skrevet til at acceptere de meget dyrere standarder alligevel.

Sp:hvad er mere effektivt, en cursor eller en while-løkke?

A: En WHILE loop vil sandsynligvis være mere effektiv end en tilsvarende markør med standardindstillingerne, men jeg formoder, at du vil finde lidt om nogen forskel, hvis du bruger LOCAL FAST_FORWARD . Generelt set en WHILE loop *er* en markør uden at blive kaldt en markør, og jeg udfordrede nogle højt skattede kolleger til at bevise, at jeg tog fejl sidste år. Deres WHILE loops klarede sig ikke så godt.

Spørgsmål:Du anbefaler ikke usp-præfiks for brugerlagrede procedurer, har dette samme negative virkning?

A: En usp_ præfiks (eller et andet præfiks end sp_ , eller intet præfiks for den sags skyld) *ikke* har den samme effekt, som jeg demonstrerede. Jeg finder dog ringe værdi i at bruge et præfiks på lagrede procedurer, fordi der meget sjældent er nogen tvivl om, at når jeg finder kode, der siger EXEC something , at noget er en lagret procedure - så der er ringe værdi der (i modsætning til f.eks. præfiksvisninger for at skelne dem fra tabeller, da de kan bruges i flæng). At give hver procedure det samme præfiks gør det også meget sværere at finde det objekt, du leder efter i f.eks. Objekt Explorer. Forestil dig, hvis hvert efternavn i telefonbogen var foranstillet med LastName_ – på hvilken måde hjælper det dig?

Sp:Er der en måde at rydde op i cachelagrede planer, hvor der er flere kopier?

A: Ja! Nå, hvis du er på SQL Server 2008 eller nyere. Når du har identificeret to planer, der er identiske, vil de stadig have separat plan_handle værdier. Så identificer den du *ikke* ønsker at beholde, kopier dens plan_handle , og læg den i denne DBCC kommando:

DBCC FREEPROCCACHE(0x06.....);
Sp.:Giver brug af if else osv. i en proc dårlige planer, bliver det optimeret til den første kørsel og kun optimeret til den sti? Så skal kodeafsnittene i hver IF laves om til separate procedurer?

A: Da SQL Server nu kan udføre optimering på statement-niveau, har dette en mindre drastisk effekt i dag, end det gjorde på ældre versioner, hvor hele proceduren skulle genkompileres som én enhed.

Sp:Jeg fandt nogle gange ud af, at det kan være bedre at skrive dynamisk sql, fordi det eliminerer parametersniffing-problemet for sp. Er det sandt ? Er der afvejninger eller andre overvejelser, der skal tages i forhold til dette scenarie?

A: Ja, dynamisk SQL kan ofte modarbejde parametersniffing, især i det tilfælde, hvor en massiv "køkkenvask"-forespørgsel har en masse valgfri parametre. Jeg behandlede nogle andre overvejelser i spørgsmål ovenfor.

Sp:Hvis jeg havde en beregnet kolonne på min tabel som DATEPART(minkolonne, år) og i indeks på den, ville SQL-serveren bruge denne med en SEEK?

A: Det burde det, men det afhænger selvfølgelig af forespørgslen. Indekset er muligvis ikke egnet til at dække outputkolonnerne eller opfylde andre filtre, og den parameter, du bruger, er muligvis ikke selektiv nok til at retfærdiggøre en søgning.

Sp.:genereres der en plan for HVER forespørgsel? Er en plan genereret selv for trivielle?

A: Så vidt jeg ved, genereres en plan for hver gyldig forespørgsel, selv trivielle planer, medmindre der er en fejl, der forhindrer en plan i at blive genereret (dette kan ske i flere scenarier, såsom ugyldige hints). Om de er cachelagret eller ej (og hvor længe de forbliver i cachen) afhænger af en række andre faktorer, hvoraf nogle jeg har diskuteret ovenfor.

Sp:Genererer (og genbruger) et kald til sp_executesql den cachelagrede plan?

A: Ja, hvis du sender nøjagtig den samme forespørgselstekst, er det lige meget, om du udsteder den direkte eller sender den gennem sp_executesql , vil SQL Server cache og genbruge planen.

Sp:Er det ok at håndhæve en regel (for et udviklermiljø), hvor alle udviklermaskiner bruger øjeblikkelig filinitialisering?

A: Jeg kan ikke se hvorfor ikke. Den eneste bekymring, jeg ville have, er, at med øjeblikkelig filinitialisering kan udviklerne ikke bemærke et stort antal autogrow-begivenheder, hvilket kan afspejle dårlige autogrowth-indstillinger, der kan have en meget anderledes indvirkning på produktionsmiljøet (især hvis nogen af ​​disse servere *ikke gør det) * har IFI aktiveret).

Sp:Med funktionen i SELECT-sætningen, ville det så være korrekt at sige, at det er bedre at duplikere kode?

A: Personligt vil jeg sige ja. Jeg har fået en masse ydeevne ud af at erstatte skalarfunktioner i SELECT liste med en inline-ækvivalent, selv i tilfælde, hvor jeg er nødt til at gentage den kode. Som nævnt ovenfor kan du dog i nogle tilfælde finde ud af, at udskiftning af den med en inline-tabel-vurderet funktion kan give dig kodegenbrug uden den grimme ydeevnestraf.

Sp:Kan vi bruge datageneratorer til at få samme datastørrelse til udviklingsbrug i stedet for at bruge (svært at få) produktionsdata? Er dataskævheden vigtig for de resulterende planer?

A: Dataskævhed kan have en faktor, og jeg formoder, at det afhænger af, hvilken slags data du genererer/simulerer, og hvor langt skævheden kan være. Hvis du f.eks. har en varchar(100)-kolonne, som i produktion typisk er 90 tegn lang, og din datagenerering producerer data, der i gennemsnit er 50 (hvilket er, hvad SQL Server vil antage), vil du finde meget forskellig indflydelse på antallet af sider og optimering, og sandsynligvis ikke særlig realistiske tests.

Men jeg skal være ærlig:Denne specifikke facet er ikke noget, jeg har investeret meget tid i, for jeg kan som regel mobbe mig til at få rigtige data. :-)

Sp:Er alle funktioner oprettet ens, når forespørgselsydeevnen undersøges? Hvis ikke, er der en liste over kendte funktioner, du bør undgå, når det er muligt?

A: Nej, ikke alle funktioner er skabt lige med hensyn til ydeevne. Der er tre forskellige typer funktioner, som vi kan oprette (ignorerer CLR-funktioner for tiden):

  • Skalære funktioner med flere sætninger
  • Tabelværdierede funktioner med flere sætninger
  • Inline-tabelvurderede funktioner
    Inline-skalarfunktioner er nævnt i dokumentationen, men de er en myte og som i SQL Server 2014 kan i hvert fald lige så godt nævnes sammen med Sasquatch og Loch Ness Monster.

Generelt, og jeg ville sætte det i 80 pkt skrift, hvis jeg kunne, er inline tabel-værdisatte funktioner gode, og de andre bør undgås, når det er muligt, da de er meget sværere at optimere.

Funktioner kan også have forskellige egenskaber, der påvirker deres ydeevne, såsom om de er deterministiske, og om de er skemabundne.

For mange funktionsmønstre er der bestemt præstationsovervejelser, du skal gøre dig, og du bør også være opmærksom på dette Connect-element, som har til formål at løse dem.

Sp:Kan vi fortsætte med at køre totaler uden markører?

A: Ja vi kan; der er flere andre metoder end en markør (som beskrevet i mit blogindlæg, Bedste metoder til at køre totaler – opdateret til SQL Server 2012):

  • Underforespørgsel i SELECT-listen
  • Rekursiv CTE
  • Tilmeld dig selv
  • "Quirky opdatering"
  • Kun SQL Server 2012+:SUM() OVER() (bruger standard / RANGE)
  • Kun SQL Server 2012+:SUM() OVER() (ved hjælp af ROWS)

Den sidste mulighed er langt den bedste tilgang, hvis du er på SQL Server 2012; hvis ikke, er der begrænsninger for de andre ikke-markør muligheder, som ofte vil gøre en markør til det mere attraktive valg. For eksempel er den finurlige opdateringsmetode udokumenteret og kan ikke garanteres at fungere i den rækkefølge, du forventer; den rekursive CTE kræver, at der ikke er huller i den sekventielle mekanisme, du bruger; og underforespørgslen og selv-join-tilgangene skaleres simpelthen ikke.


  1. Opretter forbindelse til SQL Server 2012 ved hjælp af sqlalchemy og pyodbc

  2. En oversigt over volumenniveaureplikering til PostgreSQL ved hjælp af DRBD

  3. SQL UNION-klausul for begyndere

  4. Sådan bruger du Crosstab Query Wizard i Access