I den här fortsättningen på min serie "knee-jerk performance tuning" skulle jag vilja diskutera fyra vanliga problem jag ser med att använda tillfälliga tabeller. Vilket som helst av dessa problem kan försvaga en arbetsbörda, så de är värda att känna till och leta efter i din miljö.
Problem 1:Använda tillfälliga tabeller där de inte behövs
https://www.flickr. com/photos/tea_time/3890677277/Temporära tabeller har en mängd olika användningsområden (förmodligen det vanligaste är att lagra en mellanliggande resultatuppsättning för senare användning), men du måste komma ihåg att när du introducerar en tillfällig tabell i en fråga, avbryter du dataflödet genom frågeprocessor.
Tänk på populationen av en temporär tabell som ett svårt stopp, eftersom det finns en fråga (låt oss kalla det producenten) för att producera den mellanliggande resultatuppsättningen, som sedan lagras i den temporära tabellen i tempdb, och sedan nästa fråga (låt oss anropa om konsumenten) måste läsa data från den tillfälliga tabellen igen.
Jag har ofta funnit att vissa delar av en arbetsbelastning faktiskt fungerar bättre när den temporära tabellen är helt borttagen, så data flödar från producentdelen av frågan till konsumentdelen av frågan utan att behöva hållas kvar i tempdb, och frågeoptimeraren kan skapa en mer optimal övergripande plan.
Du kanske nu tänker, "så varför skulle någon använda ett tillfälligt bord om det gör saker långsammare?" – och med rätta! I sådana fall har jag upptäckt att användningen av en tillfällig tabell har blivit institutionaliserad i utvecklingsteamet; någon upptäckte att användningen av en tillfällig tabell ökade prestandan för många år sedan, så tillfälliga tabeller blev standarddesignvalet.
Detta kan vara svårt att ändra på, speciellt om du har en senior utvecklare eller chef som är övertygad om att tillfälliga tabeller alltid ska användas. Det enkla att försöka är att välja en dyr fråga (till exempel en långvarig fråga eller en som körs många gånger per sekund) och ta bort en eller flera av de tillfälliga tabellerna för att se om prestandan ökar utan dem. Och i så fall finns det ditt bevis för att visa de oförsonliga!
Problem 2:Brist på filtrering vid fyllning av tillfälliga tabeller
Även om du inte kan ta bort en tillfällig tabell kan du kanske drastiskt förbättra prestandan genom att se till att koden som fyller den temporära tabellen korrekt filtrerar data som hämtas från källtabeller.
Jag har tappat räkningen på antalet gånger jag har sett en tillfällig tabell fyllas i med kod som börjar som SELECT *
, innehåller några oinskränkta joins och har ingen WHERE-sats, och sedan använder den senare frågan som använder den temporära tabellen bara ett fåtal kolumner och har en WHERE-sats för att minska antalet rader enormt.
Jag minns ett fall där en tillfällig tabell i en lagrad procedur aggregerade 15 års data från huvuddatabasen, och då användes bara det aktuella årets data. Detta fick upprepade gånger tempdb att växa tills det tog slut på utrymme på diskvolymen, och den lagrade proceduren skulle då misslyckas.
När du fyller i en temporär tabell, använd bara källtabellens kolumner som är nödvändiga, och använd endast de rader som är nödvändiga – d.v.s. tryck upp filterpredikaten i den temporära tabellpopulationskoden. Detta kommer inte bara att spara utrymme i tempdb, det kommer också att spara mycket tid från att inte behöva kopiera onödiga data från källtabellen (och eventuellt ta bort behovet av att läsa källdatabassidor från disken i första hand).
Problem 3:Felaktig temporär tabellindexering
Precis som med vanliga tabeller bör du bara skapa de index som faktiskt kommer att användas av den senare frågekoden för att hjälpa frågans prestanda. Jag har sett många fall där det finns ett icke-klustrat index per tillfällig tabellkolumn, och index med en kolumn som väljs utan att analysera den senare koden är ofta ganska värdelösa. Kombinera nu värdelösa icke-klustrade index med brist på filtrering när du fyller i den tillfälliga tabellen, och du har ett recept för enorm uppblåsthet av tempdb.
Dessutom är det i allmänhet snabbare att skapa indexen efter att tabellen har fyllts i. Detta ger den extra bonusen att indexen kommer att ha korrekt statistik, vilket ytterligare kan hjälpa frågan eftersom frågeoptimeraren kommer att kunna göra korrekt kardinalitetsuppskattning.
Att ha ett gäng icke-klustrade index som inte används slösar inte bara diskutrymme utan också tiden som behövs för att skapa dem. Om detta finns i kod som körs ofta kan det ha en betydande effekt på den övergripande prestandan att ta bort dessa onödiga index som skapas varje gång koden körs.
Problem 4:tempdb Latch Contention
Det är ganska vanligt att det finns en låsande flaskhals i tempdb som kan spåras tillbaka till tillfällig tabellanvändning. Om det finns många samtidiga anslutningar som kör kod som skapar och släpper tillfälliga tabeller, kan åtkomst till databasens allokeringsbitmappar i minnet bli en betydande flaskhals.
Detta beror på att endast en tråd åt gången kan ändra en tilldelningsbitmapp för att markera sidor (från temptabellen) som tilldelade eller avallokerade, och så att alla andra trådar måste vänta, vilket minskar arbetsbelastningen. Även om det har funnits en tillfällig tabellcache sedan SQL Server 2005, är den inte särskilt stor, och det finns begränsningar för när den temporära tabellen kan cachelagras (t.ex. endast när den är mindre än 8 MB i storlek).
Traditionella sätt att kringgå detta problem har varit att använda trace flag 1118 och flera tempdb-datafiler (se det här blogginlägget för mer information), men en annan sak att tänka på är att ta bort de temporära tabellerna helt och hållet!
Sammanfattning
Tillfälliga tabeller kan vara mycket användbara, men de är mycket lätta och ofta felaktigt använda. När du skriver (eller granskar kod) som använder en tillfällig tabell, tänk på följande:
- Är den här tillfälliga tabellen behövs verkligen ?
- Är koden som fyller tabellen med rätt filtrering för att begränsa den tillfälliga tabellstorleken?
- Är index skapade efter tabellpopulation (i allmänhet) och är indexen som används med senare kod?
Paul White har ett par bra inlägg (här och här) om temporär objektanvändning och cachning som jag rekommenderar att du också läser.
Och en sista sak, om du bestämmer dig för att inte använda en temporär tabell, ersätt den inte bara med en tabellvariabel, ett vanligt tabelluttryck eller en markör (som alla är vanliga sätt som folk försöker "optimera bort" temporär tabell) – ta reda på det mest effektiva sättet att (om)skriva koden – det finns inget "one size fits all"-svar.
Tills nästa gång, lycklig felsökning!