I denne fortsættelse af min "knee-jerk performance tuning"-serie vil jeg gerne diskutere fire almindelige problemer, jeg ser med at bruge midlertidige tabeller. Ethvert af disse problemer kan lamme en arbejdsbyrde, så de er værd at kende til og lede efter i dit miljø.
Problem 1:Brug af midlertidige tabeller, hvor de ikke er nødvendige
https://www.flickr. com/photos/tea_time/3890677277/Midlertidige tabeller har en række anvendelser (sandsynligvis den mest almindelige er at gemme et mellemresultatsæt til senere brug), men du skal huske, at når du introducerer en midlertidig tabel i en forespørgsel, afbryder du datastrømmen gennem forespørgselsprocessor.
Tænk på populationen af en midlertidig tabel som et hårdt stop, da der er en forespørgsel (lad os kalde det producenten) for at producere det mellemliggende resultatsæt, som derefter gemmes i den midlertidige tabel i tempdb, og derefter den næste forespørgsel (lad os kalde hvis forbrugeren) skal læse dataene fra den midlertidige tabel igen.
Jeg har ofte oplevet, at nogle dele af en arbejdsbelastning faktisk fungerer bedre, når den midlertidige tabel er fuldstændig fjernet, så dataene flyder fra producentdelen af forespørgslen til forbrugerdelen af forespørgslen uden at skulle fortsættes i tempdb, og query optimizer kan producere en mere optimal overordnet plan.
Du tænker måske nu, "så hvorfor skulle nogen bruge et midlertidigt bord, hvis det gør tingene langsommere?" – og med rette! I sådanne tilfælde har jeg oplevet, at brugen af en midlertidig tabel er blevet institutionaliseret i udviklingsteamet; nogen fandt ud af, at brugen af en midlertidig tabel øgede ydeevnen for mange år siden, så midlertidige tabeller blev standarddesignvalget.
Dette kan være svært at ændre på, især hvis du har en seniorudvikler eller leder, der er overbevist om, at midlertidige tabeller altid skal bruges. Den enkle ting at prøve er at vælge en dyr forespørgsel (for eksempel en langvarig, eller en, der er udført mange gange i sekundet) og fjerne en eller flere af de midlertidige tabeller for at se, om ydeevnen øges uden dem. Og hvis ja, så er der dit bevis for at vise de uforsonlige!
Problem 2:Manglende filtrering ved udfyldning af midlertidige tabeller
Selvom du ikke kan fjerne en midlertidig tabel, kan du muligvis forbedre ydeevnen drastisk ved at sikre dig, at koden, der udfylder den midlertidige tabel, korrekt filtrerer data hentet fra kildetabeller.
Jeg har mistet tællingen af antallet af gange, jeg har set en midlertidig tabel blive udfyldt med kode, der starter som SELECT *
, inkluderer et par ubegrænsende joinforbindelser og har ingen WHERE-udtryk, og så bruger den senere forespørgsel, der bruger den midlertidige tabel, kun nogle få kolonner og har en WHERE-udtryk for at begrænse antallet af rækker enormt.
Jeg husker et tilfælde, hvor en midlertidig tabel i en lagret procedure samlede 15 års data fra hoveddatabasen, og så blev kun det aktuelle års data brugt. Dette fik gentagne gange tempdb til at vokse, indtil den løb tør for plads på diskenheden, og den lagrede procedure ville derefter mislykkes.
Når du udfylder en midlertidig tabel, skal du kun bruge de kildetabelkolonner, der er nødvendige, og kun bruge de nødvendige rækker - dvs. skub filterprædikaterne op i den midlertidige tabelpopulationskode. Dette vil ikke kun spare plads i tempdb, det vil også spare en masse tid fra ikke at skulle kopiere unødvendige data fra kildetabellen (og potentielt fjerne behovet for at læse kildedatabasesider fra disk i første omgang).
Problem 3:Forkert midlertidig tabelindeksering
Ligesom med almindelige tabeller, bør du kun oprette de indekser, der faktisk skal bruges af den senere forespørgselskode for at hjælpe forespørgslens ydeevne. Jeg har set masser af tilfælde, hvor der er et ikke-klynget indeks pr. midlertidig tabelkolonne, og enkeltkolonneindekser, der vælges uden at analysere den senere kode, ofte er ret ubrugelige. Kombiner nu ubrugelige ikke-klyngede indekser med mangel på filtrering, når du udfylder den midlertidige tabel, og du har en opskrift på enorm oppustethed af tempdb.
Generelt er det også hurtigere at oprette indekserne, efter at tabellen er blevet udfyldt. Dette giver den ekstra bonus, at indekserne vil have nøjagtige statistikker, hvilket yderligere kan hjælpe forespørgslen, da forespørgselsoptimeringsværktøjet vil være i stand til at foretage nøjagtig kardinalitetsestimat.
At have en masse ikke-klyngede indekser, der ikke bruges, spilder ikke kun diskplads, men også den tid, der er nødvendig for at oprette dem. Hvis dette er i kode, der udføres ofte, kan fjernelse af disse unødvendige indekser, der oprettes, hver gang koden kører, have en betydelig effekt på den samlede ydeevne.
Problem 4:tempdb Latch Contention
Det er ret almindeligt, at der er en låsende flaskehals i tempdb, der kan spores tilbage til midlertidig bordbrug. Hvis der er masser af samtidige forbindelser, der kører kode, der opretter og sletter midlertidige tabeller, kan adgang til databasens allokeringsbitmaps i hukommelsen blive en betydelig flaskehals.
Dette skyldes, at kun én tråd ad gangen kan ændre en allokeringsbitmap for at markere sider (fra temp-tabellen) som allokerede eller deallokerede, og derfor skal alle de andre tråde vente, hvilket mindsker arbejdsbyrden. Selvom der har været en midlertidig tabelcache siden SQL Server 2005, er den ikke særlig stor, og der er begrænsninger for, hvornår den midlertidige tabel kan cachelagres (f.eks. kun når den er mindre end 8MB i størrelse).
Traditionelle måder at løse dette problem på har været at bruge trace flag 1118 og flere tempdb-datafiler (se dette blogindlæg for mere info), men en anden ting at overveje er at fjerne de midlertidige tabeller helt!
Oversigt
Midlertidige tabeller kan være meget nyttige, men de er meget nemme og almindeligvis brugt forkert. Når du skriver (eller gennemgår kode), der bruger en midlertidig tabel, skal du overveje følgende:
- Er denne midlertidige tabel virkelig nødvendig ?
- Er koden, der udfylder tabellen, ved hjælp af den korrekte filtrering for at begrænse den midlertidige tabelstørrelse?
- Er indekser oprettet efter tabelpopulation (generelt) og er de indekser, der bruges med senere kode?
Paul White har et par gode indlæg (her og her) om midlertidig objektbrug og caching, som jeg også anbefaler at læse.
Og en sidste ting, hvis du beslutter dig for ikke at bruge en midlertidig tabel, skal du ikke bare erstatte den med en tabelvariabel, et almindeligt tabeludtryk eller en markør (som alle er almindelige måder, som folk forsøger at "optimere væk" midlertidig tabel) – find ud af den mest effektive måde at (gen)skrive koden på – der er ikke noget "one size fits all"-svar.
Indtil næste gang, god fejlfinding!