Når en eksekveringsplan inkluderer en scanning af en b-tree indeksstruktur, kan kunne vælge mellem to fysiske adgangsstrategier, når planen eksekveres:
- Følg indeksets b-træstruktur; eller
- find sider ved hjælp af interne sidetildelingsoplysninger.
Hvor et valg er tilgængeligt, træffer storage-motoren køretidsbeslutningen for hver udførelse. En plangenkompilering er ikke kræves for, at den ændrer mening.
B-træstrategien starter ved roden af træet, falder ned til en yderste kant af bladniveauet (afhængigt af om scanningen er fremad eller bagud), og følger derefter sidelinks på bladniveau, indtil den anden ende af indekset nås . Tildelingsstrategien bruger Index Allocation Map (IAM) strukturer til at lokalisere databasesider, der er allokeret til indekset. Hver IAM-side kortlægger allokeringer til et 4 GB-interval i en enkelt fysisk databasefil, så scanning af IAM-kæder, der er knyttet til et indeks, har en tendens til at få adgang til indekssider i fysisk filrækkefølge (i det mindste så vidt SQL Server kan se).
De vigtigste forskelle mellem de to strategier er:
- En b-tree-scanning kan levere rækker til forespørgselsprocessoren i indeksnøglerækkefølge; en IAM-drevet scanning kan ikke;
- en b-tree-scanning er muligvis ikke i stand til at udstede store I/O-anmodninger, hvis logisk sammenhængende indekssider ikke også er fysisk sammenhængende (f.eks. som følge af sideopdeling i indekset).
En b-træ scanning er altid tilgængelig for et indeks. Betingelserne, der ofte nævnes for, at allokeringsordrescanninger er tilgængelige, er:
- Forespørgselsplanen skal tillade en uordnet scanning af indekset;
- indekset skal være på mindst 64 sider. og,
- enten en
TABLOCK
ellerNOLOCK
tip skal angives.
Den første betingelse betyder blot, at forespørgselsoptimeringsværktøjet skal have markeret scanningen med Ordered:False
ejendom. Markering af scanningen Ordered:False
betyder, at korrekte resultater fra udførelsesplanen ikke kræver scanningen for at returnere rækker i indeksnøglerækkefølge (selvom den kan gøre det, hvis det er praktisk eller på anden måde nødvendigt).
Den anden betingelse (størrelse) gælder kun for SQL Server 2005 og nyere. Det afspejler, at der er en vis opstartsomkostning ved at udføre en IAM-drevet scanning, så der skal være et minimum antal sider for, at de potentielle besparelser kan tilbagebetale den oprindelige investering. "64 sider" refererer til værdien af data_sider for IN_ROW_DATA
kun tildelingsenhed, som rapporteret i sys.allocation_units.
Selvfølgelig kan der kun være et udbytte fra en tildelingsordrescanning, hvis de muligvis større read-ahead overvejelser faktisk kommer i spil, men SQL Server overvejer i øjeblikket ikke denne faktor. Især tager den ikke højde for, hvor meget af indekset der i øjeblikket er i hukommelsen, og det er heller ikke ligeglad med, hvor fragmenteret indekset er.
Den tredje betingelse er sandsynligvis den mindst fuldstændige beskrivelse på listen. Tip er faktisk ikke påkrævet , selvom de kan bruges til at opfylde de reelle krav:Dataene skal garanteret ikke ændres under scanningen, eller (mere kontroversielt) skal vi indikere, at vi er ligeglade om potentielt unøjagtige resultater ved at udføre scanningen på læst uforpligtende isolationsniveau.
Selv med disse præciseringer er listen over betingelser for en tildelingsbeordret scanning stadig ikke komplet. Der er en række vigtige forbehold og undtagelser, som vi vil komme ind på om kort tid.
Demo
Følgende forespørgsel bruger AdventureWorks-eksempeldatabasen:
CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P;
Bemærk, at Person-tabellen indeholder 3.869 sider. Planen efter udførelse (faktisk) er som følger (vist i SQL Sentry Plan Explorer):
Med hensyn til tildelings-ordre-scanningskravene har vi indtil videre:
- Planen har den påkrævede
Ordered:False
ejendom; og, - tabellen har mere end 64 sider; men,
- vi har ikke gjort noget for at sikre, at dataene ikke kan ændre sig under scanningen. Hvis vi antager, at vores session bruger standarden læse-committed isolationsniveau, udføres scanningen ikke ved læst ukommitteret isolationsniveau enten.
Som en konsekvens ville vi forvente, at denne scanning udføres ved at scanne b-træet i stedet for at være IAM-drevet. Forespørgselsresultaterne indikerer, at dette sandsynligvis er sandt:
Rækkerne returneres i Clustered Index nøglerækkefølge (efter BusinessEntityID
). Jeg bør klart sige, at denne resultatrækkefølge er absolut ikke garanteret , og bør ikke stoles på. Bestilte resultater er kun garanteret af en passende ORDER BY
på øverste niveau klausul.
Ikke desto mindre er den observerede outputrækkefølge indicier for, at scanningen blev udført denne gang ved at følge den klyngede indeks b-træstruktur. Hvis der er behov for mere bevis, kan vi vedhæfte en debugger og se på kodestien SQL Server udfører under scanningen:
Opkaldsstakken viser tydeligt scanningen efter b-træet.
Tilføjelse af et bordlåstip
Vi ændrer nu forespørgslen til at inkludere et tabellåstip:
CHECKPOINT; DBCC DROPCLEANBUFFERS; SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK);
På standardlåsnings-læse-forpligtet isolationsniveau forhindrer den delte tabel-niveaulås enhver mulig samtidig modifikation af dataene. Med alle tre forudsætninger for IAM-drevne scanninger opfyldt, ville vi nu forvente, at SQL Server bruger en allokeringsordre-scanning. Udførelsesplanen er den samme som før, så jeg vil ikke gentage den, men forespørgselsresultaterne ser bestemt anderledes ud:
Resultaterne er tilsyneladende stadig sorteret efter BusinessEntityID
, men udgangspunktet (10866) er anderledes. Faktisk, hvis vi ruller ned i resultaterne, støder vi snart på sektioner, der er mere åbenlyst ude af nøgleorden:
Den delvise bestilling skyldes, at allokeringsordrescanningen behandler en hel indeksside ad gangen. Resultaterne inden for en side tilfældigvis returneres sorteret efter indeksnøglen, men rækkefølgen af de scannede sider er nu anderledes. Igen skal jeg understrege, at resultaterne kan se anderledes ud for dig:der er ingen garanti for outputrækkefølge, selv inden for en side, uden en ORDER BY
på øverste niveau. på den oprindelige forespørgsel.
Til sammenligning med opkaldsstakken vist tidligere, er dette en staksporing opnået, mens SQL Server behandlede forespørgslen med TABLOCK
tip:
Træder lidt videre gennem udførelsen:
Det er klart, at SQL Server udfører en allokeringsordret scanning, når tabellåsen er angivet. Det er en skam, at der ikke er nogen indikation i en post-udførelsesplan af, hvilken type scanning der blev brugt under kørsel. Som en påmindelse vælges typen af scanning af lagringsmotoren og kan ændres mellem udførelser uden en plangenkompilering.
Andre måder at opfylde den tredje betingelse
Jeg sagde før, at for at få en IAM-drevet scanning, skal vi sikre, at dataene ikke kan ændre sig under scanningen, mens den er i gang, eller vi er nødt til at køre forespørgslen på læse-uforpligtet isolationsniveau. Vi har set, at et bordlås-tip om låsning af læseforpligtet isolation er tilstrækkeligt til at opfylde det første af disse krav, og det er nemt at vise, at brug af en NOLOCK/READUNCOMMITTED
tip muliggør også en allokeringsordrescanning med demoforespørgslen.
Faktisk er der mange måder at opfylde den tredje betingelse, herunder:
- Ændring af indekset til kun at tillade tabellåse;
- gør databasen skrivebeskyttet (så data er garanteret ikke ændres); eller,
- ændring af sessionen isolationsniveau til
READ UNCOMMITTED
.
Der er dog meget mere interessante variationer over dette tema, som betyder, at vi er nødt til at ændre de tre tidligere nævnte betingelser...
Isolationsniveauer for rækkeversionering
Aktiver read committed snapshot isolation (RCSI) på AdventureWorks-databasen, og kør testen med TABLOCK
hint igen (ved læst forpligtet isolation):
ALTER DATABASE AdventureWorks2012 SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK); GO ALTER DATABASE AdventureWorks2012 SET READ_COMMITTED_SNAPSHOT OFF WITH ROLLBACK IMMEDIATE;
Med RCSI aktiv, en indeksordnet scanning bruges med TABLOCK
, ikke den allokeringsordre-scanning, vi så lige før. Årsagen er TABLOCK
tip angiver en delt lås på tabelniveau, men med RCSI aktiveret ingen delte låse er taget. Uden den delte tabellås har vi ikke opfyldt kravet om at forhindre samtidige ændringer af dataene, mens scanningen er i gang, så en allokeringsbestemt scanning kan ikke bruges.
Det er dog muligt at opnå en allokeringsbestemt scanning, når RCSI er aktiveret. En måde er at bruge en TABLOCKX
tip (for en eksklusiv tabel på tabelniveau lås) i stedet for TABLOCK
. Vi kunne også beholde TABLOCK
tip og tilføj endnu en som READCOMMITTEDLOCK
, eller REPEATABLE READ
eller SERIALIZABLE
… og så videre. Alle disse virker ved at forhindre muligheden for samtidige ændringer ved at tage en delt bordlås på bekostning af at miste fordelene ved RCSI . Vi kan også stadig opnå en allokeringsordre-scanning ved hjælp af en NOLOCK
eller READUNCOMMITTED
tip, selvfølgelig.
Situationen under snapshot isolation (SI) ligner meget RCSI og er ikke udforsket i detaljer af pladshensyn.
TABLESAMPLE udfører altid* en allokeringsordrescanning
TABLESAMPLE
klausulen er en interessant undtagelse fra mange af de ting, vi har diskuteret indtil nu.
Angivelse af en TABLESAMPLE
klausul altid* resulterer i en allokeringsordrescanning, selv under RCSI eller SI, og endda uden hints. For at være klar over det, scanningen af allokeringsordre, der er resultatet af brugen af TABLESAMPLE
bevarer RCSI/SI-semantik – scanningen bruger rækkeversioner, og læsning blokerer ikke for skrivning (og omvendt).
En anden overraskelse er, at TABLESAMPLE
udfører altid* en IAM-drevet scanning selvom tabellen har færre end 64 sider . Dette giver mening, fordi dokumentationen i det mindste antyder, at SYSTEM
prøveudtagningsmetoden bruger IAM-strukturen (så der er intet andet valg end at lave en allokeringsordrescanning):
SYSTEM Er en implementeringsafhængig prøveudtagningsmetode specificeret af ISO-standarder. I SQL Server er dette den eneste tilgængelige prøvemetode og anvendes som standard. SYSTEM anvender en sidebaseret stikprøvemetode, hvor et tilfældigt sæt sider fra tabellen vælges til prøven, og alle rækkerne på disse sider returneres som prøveundermængden.
* En undtagelse opstår, hvis ROWS
eller PERCENT
specifikation i TABLESAMPLE
klausul betyder 100 % af tabellen. Angivelse af flere ROWS
end metadataene angiver i øjeblikket er i tabellen, vil heller ikke virke. Brug af TABLESAMPLE SYSTEM (100 PERCENT)
eller tilsvarende vil ikke fremtvinge en allokeringsordre-scanning.
CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P TABLESAMPLE SYSTEM (50 ROWS) REPEATABLE (12345678) --WITH (TABLOCK);
Resultater:
Effekten af TOP og SET ROWCOUNT
Kort sagt, ingen af disse har nogen indflydelse på beslutningen om at bruge en tildelingsordre-scanning eller ej. Dette kan virke overraskende i tilfælde, hvor det er "indlysende", at færre end 64 sider vil blive scannet.
For eksempel bruger følgende forespørgsler begge en IAM-drevet scanning til at returnere 5 rækker fra en scanning:
SELECT TOP (5) P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK) SET ROWCOUNT 5; SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK) SET ROWCOUNT 0;
Resultaterne er de samme for begge:
Det betyder, at TOP
og SET ROWCOUNT
forespørgsler måske pådrage sig omkostningerne ved opsætning af en tildelings-ordre scanning, selvom færre end 64 sider er scannet. Som afhjælpning kunne mere komplekse TOP-forespørgsler med selektive prædikater skubbet ind i scanningen stadig drage fordel af en allokeringsordre-scanning. Hvis scanningen skal behandle 10.000 sider for at finde de første 5 rækker, der matcher, kan en tildelingsordrescanning stadig være en gevinst.
Forhindring af alle* allokeringsordrescanninger i hele instansen
Dette er ikke noget, du sandsynligvis ville gøre med vilje, men der er en serverindstilling, der forhindrer scanninger af allokeringsordre for alle* brugerforespørgsler i alle databaser.
Hvor usandsynligt det kan virke, er den pågældende indstilling konfigurationsmuligheden for markørtærskelserveren, som har følgende beskrivelse i Books Online:
Markørtærskelindstillingen angiver antallet af rækker i markørsættet, hvor markørtastesæt genereres asynkront. Når markører genererer et nøglesæt for et resultatsæt, estimerer forespørgselsoptimeringsværktøjet antallet af rækker, der returneres for det resultatsæt. Hvis forespørgselsoptimeringsværktøjet estimerer, at antallet af returnerede rækker er større end denne tærskel, genereres markøren asynkront, hvilket giver brugeren mulighed for at hente rækker fra markøren, mens markøren fortsætter med at blive udfyldt. Ellers genereres markøren synkront, og forespørgslen venter, indtil alle rækker er returneret.
Hvis cursor threshold
indstillingen er sat til noget andet end –1 (standard), vil der ikke forekomme scanninger af allokeringsrækkefølge for brugerforespørgsler i nogen database på SQL Server-instansen.
Med andre ord, hvis asynkron markørpopulation er aktiveret, scanner ingen IAM-drevet for dig.
* Undtagelsen er (ikke-100%) TABLESAMPLE
forespørgsler. De interne forespørgsler genereret af systemet til statistikoprettelse og statistikopdateringer fortsætter også med at bruge allokeringsordrede scanninger.
CHECKPOINT; DBCC DROPCLEANBUFFERS; GO -- WARNING! Disables allocation-order scans instance-wide EXECUTE sys.sp_configure @configname = 'cursor threshold', @configvalue = 5000; RECONFIGURE WITH OVERRIDE; GO -- Would normally result in an allocation-order scan SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (READUNCOMMITTED); GO -- Reset to default allocation-order scans EXECUTE sys.sp_configure @configname = 'cursor threshold', @configvalue = -1; RECONFIGURE WITH OVERRIDE;
Resultater (ingen allokeringsordrescanning):
Man kan kun gætte på, at asynkron markørpopulation af en eller anden grund ikke fungerer godt med allokeringsordre-scanninger. Det er helt uventet, at denne begrænsning vil påvirke alle brugerforespørgsler uden markør dog også. Måske er det for svært for SQL Server at opdage, om en forespørgsel kører som en del af en eksternt udstedt API-markør? Hvem ved.
Det ville være rart, hvis denne bivirkning blev officielt dokumenteret et sted, selvom det er svært at vide præcis, hvor det skal gå hen i Books Online. Jeg spekulerer på, hvor mange produktionssystemer derude, der ikke bruger allokeringsordrescanninger på grund af dette? Måske ikke mange, men man ved aldrig.
For at afslutte tingene, er her et resumé. En tildelingsbestemt scanning er tilgængelig, hvis:
- Serverindstillingen
cursor threshold
er indstillet til –1 (standardindstillingen); og - Forespørgselsplanens scanningsoperator har
Ordered:False
ejendom; og - det samlede antal data_sider af
IN_ROW_DATA
tildelingsenheder er mindst 64; og - enten:
- SQL Server har en acceptabel garanti for, at samtidige ændringer er umulige; eller,
- scanningen kører på læst uforpligtende isolationsniveau.
Uanset alt ovenstående, en scanning med en TABLESAMPLE
klausul bruger altid allokeringsordrede scanninger (med den ene tekniske undtagelse, der er noteret i hovedteksten).