I den här artikeln kommer vi att beröra ämnet prestanda för tabellvariabler. I SQL Server kan vi skapa variabler som fungerar som kompletta tabeller. Kanske har andra databaser samma möjligheter, men jag använde sådana variabler bara i MS SQL Server.
Således kan du skriva följande:
declare @t as table (int value)
Här deklarerar vi @t-variabeln som en tabell som kommer att innehålla en enda värdekolumn av heltalstypen. Det är möjligt att skapa mer komplexa tabeller, men i vårt exempel räcker en kolumn för att utforska optimeringen.
Nu kan vi använda denna variabel i våra frågor. Vi kan lägga till mycket data till den och utföra datahämtning från denna variabel:
insert into @t select UserID from User or select * from @t
Jag märkte att tabellvariabler används när det är nödvändigt att hämta data för ett stort urval. Till exempel finns det en fråga i koden som returnerar användare av webbplatsen. Nu samlar du in ID:n för alla användare, lägger till dem i tabellvariabeln och kan söka adresser för dessa användare. Kanske kan någon fråga varför vi inte kör en fråga i databasen och får allt direkt? Jag har ett enkelt exempel.
Antag att användare kommer från webbtjänsten medan deras adresser lagras i din databas. I det här fallet finns det ingen väg ut. Vi fick ett gäng användar-ID från tjänsten, och för att undvika att fråga databasen bestämmer någon att det är lättare att lägga till alla ID:n till frågeparametern som en tabellvariabel och frågan kommer att se snyggt ut:
select * from @t as users join Address a on a.UserID = users.UserID os
Allt detta fungerar korrekt. I C#-koden kan du snabbt kombinera resultaten av båda datamatriserna till ett objekt med hjälp av LINQ. Prestandan för frågan kan dock bli lidande.
Faktum är att tabellvariabler inte utformades för att bearbeta stora datamängder. Om jag inte har fel kommer frågeoptimeraren alltid att använda LOOP-exekveringsmetoden. För varje ID från @t kommer alltså en sökning i adresstabellen att ske. Om det finns 1000 poster i @t, kommer servern att skanna Adress 1000 gånger.
När det gäller exekvering, på grund av det vansinniga antalet skanningar, slutar servern helt enkelt att försöka hitta data.
Det är mycket mer effektivt att skanna hela adresstabellen och hitta alla användare på en gång. Denna metod kallas MERGE. Däremot väljer SQL Server det när det finns mycket sorterad data. I det här fallet vet optimeraren inte hur mycket och vilken data som kommer att läggas till variabeln, och om det finns sortering eftersom en sådan variabel inte inkluderar index.
Om det finns lite data i tabellvariabeln och du inte infogar tusentals rader i den, är allt bra. Men om du gillar att använda sådana variabler och lägga till en enorm mängd data till dem måste du fortsätta läsa.
Även om du ersätter tabellvariabeln med SQL, kommer det att avsevärt påskynda frågeprestanda:
select * from ( Select 10377 as UserID Union all Select 73736 Union all Select 7474748 …. ) as users join Address a on a.UserID = users.UserID
Det kan finnas tusen sådana SELECT-satser och frågetexten kommer att vara enorm, men den kommer att exekveras tusentals gånger snabbare för en stor mängd data eftersom SQL Server kan välja en effektiv exekveringsplan.
Den här frågan ser inte bra ut. Dess exekveringsplan kan dock inte cachelagras eftersom ändring av endast ett ID kommer att ändra hela frågetexten också och parametrar kan inte användas.
Jag tror att Microsoft inte förväntade sig att användare skulle använda tabellvariabler på det här sättet, men det finns en bra lösning.
Det finns flera sätt att lösa detta problem. Men enligt min åsikt är det mest effektiva när det gäller prestanda att lägga till OPTION (RECOMPILE) i slutet av frågan:
select * from @t as users join Address a on a.UserID = users.UserID OPTION (RECOMPILE)
Det här alternativet läggs till en gång i slutet av frågan efter jämn ORDER BY. Syftet med detta alternativ är att få SQL Server att kompilera om frågan vid varje körning.
Om vi mäter frågeprestanda efter det kommer tiden med största sannolikhet att minska för att utföra sökningen. Med stor data kan prestandaförbättringen vara betydande, från tiotals minuter till sekunder. Nu kompilerar servern sin kod innan varje fråga körs och använder inte exekveringsplanen från cachen, utan genererar en ny, beroende på mängden data i variabeln, och detta brukar hjälpa mycket.
Nackdelen är att exekveringsplanen inte lagras och servern måste kompilera frågan och leta efter en effektiv exekveringsplan varje gång. Jag har dock inte sett frågorna där denna process tog mer än 100 ms.
Är det en dålig idé att använda tabellvariabler? Nej det är det inte. Kom bara ihåg att de inte skapades för stora data. Ibland är det bättre att skapa en tillfällig tabell, om det finns mycket data, och infoga data i denna tabell, eller till och med skapa ett index i farten. Jag var tvungen att göra detta med rapporter, men bara en gång. Då minskade jag tiden för att skapa en rapport från 3 timmar till 20 minuter.
Jag föredrar att använda en stor fråga istället för att dela upp den i flera frågor och lagringen resulterar i variabler. Tillåt SQL Server att justera prestandan för en stor fråga och den kommer inte att svika dig. Observera att du bara bör ta till tabellvariabler i extrema fall när du verkligen ser fördelarna med dem.