I denne artikel skal vi berøre emnet for tabelvariables ydeevne. I SQL Server kan vi oprette variabler, der fungerer som komplette tabeller. Måske har andre databaser de samme muligheder, men jeg brugte kun sådanne variabler i MS SQL Server.
Således kan du skrive følgende:
declare @t as table (int value)
Her erklærer vi @t-variablen som en tabel, der vil indeholde en enkelt værdikolonne af typen heltal. Det er muligt at oprette mere komplekse tabeller, men i vores eksempel er én kolonne tilstrækkelig til at udforske optimeringen.
Nu kan vi bruge denne variabel i vores forespørgsler. Vi kan tilføje mange data til det og udføre datahentning fra denne variabel:
insert into @t select UserID from User or select * from @t
Jeg har bemærket, at tabelvariabler bruges, når det er nødvendigt at hente data til et stort udvalg. For eksempel er der en forespørgsel i koden, der returnerer brugere af webstedet. Nu indsamler du ID'er for alle brugere, tilføjer dem til tabelvariablen og kan søge adresser for disse brugere. Måske vil nogen spørge, hvorfor vi ikke udfører én forespørgsel på databasen og får alt med det samme? Jeg har et simpelt eksempel.
Antag, at brugere kommer fra webtjenesten, mens deres adresser er gemt i din database. I dette tilfælde er der ingen vej ud. Vi fik en masse bruger-id'er fra tjenesten, og for at undgå at forespørge databasen, beslutter nogen, at det er nemmere at tilføje alle id'erne til forespørgselsparameteren som en tabelvariabel, og forespørgslen vil se pænt ud:
select * from @t as users join Address a on a.UserID = users.UserID os
Alt dette fungerer korrekt. I C#-koden kan du hurtigt kombinere resultaterne af begge dataarrays til ét objekt ved hjælp af LINQ. Dog kan udførelsen af forespørgslen lide.
Faktum er, at tabelvariabler ikke er designet til at behandle store mængder data. Hvis jeg ikke tager fejl, vil forespørgselsoptimeringsværktøjet altid bruge LOOP-udførelsesmetoden. For hvert ID fra @t vil der således ske en søgning i Adressetabellen. Hvis der er 1000 poster i @t, vil serveren scanne adressen 1000 gange.
Med hensyn til udførelse, på grund af det vanvittige antal scanninger, dropper serveren simpelthen at forsøge at finde data.
Det er meget mere effektivt at scanne hele adressetabellen og finde alle brugerne på én gang. Denne metode kaldes MERGE. SQL Server vælger det dog, når der er mange sorterede data. I dette tilfælde ved optimeringsværktøjet ikke, hvor meget og hvilke data der vil blive tilføjet til variablen, og om der er sortering, fordi en sådan variabel ikke inkluderer indekser.
Hvis der er lidt data i tabelvariablen, og du ikke indsætter tusindvis af rækker i den, er alt i orden. Men hvis du kan lide at bruge sådanne variabler og tilføje en enorm mængde data til dem, skal du fortsætte med at læse.
Selv hvis du erstatter tabelvariablen med SQL, vil det i høj grad fremskynde forespørgselsydeevnen:
select * from ( Select 10377 as UserID Union all Select 73736 Union all Select 7474748 …. ) as users join Address a on a.UserID = users.UserID
Der kan være tusindvis af sådanne SELECT-sætninger, og forespørgselsteksten vil være enorm, men den vil blive udført tusindvis af gange hurtigere for en stor mængde data, fordi SQL Server kan vælge en effektiv eksekveringsplan.
Denne forespørgsel ser ikke fantastisk ud. Dens eksekveringsplan kan dog ikke cachelagres, fordi ændring af kun ét ID vil også ændre hele forespørgselsteksten, og parametre kan ikke bruges.
Jeg tror, at Microsoft ikke forventede, at brugerne skulle bruge tabelvariabler på denne måde, men der er en god løsning.
Der er flere måder at løse dette problem på. Men efter min mening er det mest effektive med hensyn til ydeevne at tilføje OPTION (RECOMPILE) til slutningen af forespørgslen:
select * from @t as users join Address a on a.UserID = users.UserID OPTION (RECOMPILE)
Denne mulighed tilføjes én gang til allersidst i forespørgslen efter ORDER BY. Formålet med denne mulighed er at få SQL Server til at rekompilere forespørgslen ved hver udførelse.
Hvis vi måler forespørgselsydelsen derefter, vil tiden højst sandsynligt blive reduceret til at udføre søgningen. Med store data kan ydeevneforbedringen være betydelig, fra snesevis af minutter til sekunder. Nu kompilerer serveren sin kode, før hver forespørgsel køres, og bruger ikke udførelsesplanen fra cachen, men genererer en ny, afhængigt af mængden af data i variablen, og det hjælper normalt meget.
Ulempen er, at eksekveringsplanen ikke er gemt, og serveren skal kompilere forespørgslen og lede efter en effektiv eksekveringsplan hver gang. Jeg har dog ikke set de forespørgsler, hvor denne proces tog mere end 100 ms.
Er det en dårlig idé at bruge tabelvariabler? Nej det er ikke. Bare husk, at de ikke er skabt til store data. Nogle gange er det bedre at oprette en midlertidig tabel, hvis der er mange data, og indsætte data i denne tabel, eller endda oprette et indeks i farten. Jeg var nødt til at gøre dette med rapporter, dog kun én gang. Dengang reducerede jeg tiden til at generere én rapport fra 3 timer til 20 minutter.
Jeg foretrækker at bruge en stor forespørgsel i stedet for at opdele den i flere forespørgsler, og lagringen resulterer i variabler. Tillad SQL Server at justere ydeevnen af en stor forespørgsel, og den vil ikke svigte dig. Bemærk venligst, at du kun bør ty til tabelvariabler i ekstreme tilfælde, når du virkelig ser deres fordele.