En af de tilgængelige algoritmer til at forbinde to tabeller i SQL Server er Nested Loops. Den indlejrede sløjfesammenføjning bruger én join-input som den ydre inputtabel og en som den indre inputtabel. Den ydre sløjfe gentager den ydre inputtabel række for række. Den indre løkke, der udføres for hver ydre række, søger efter matchende rækker i den indre inputtabel.
Dette kaldes en naiv indlejret loops join.
Hvis du har et indeks på joinbetingelser i den indre inputtabel, er det ikke nødvendigt at udføre en indre løkke for hver række i den ydre tabel. I stedet kan du sende værdien fra den eksterne tabel som et søgeargument og forbinde alle de returnerede rækker i den indre tabel til rækkerne fra den ydre tabel.
Søgningen ved den indre tabel er en tilfældig adgang. SQL Server, der starter fra version 2005, har batchsorteringsoptimering (må ikke forveksles med Sort-operatoren i Batch Mode for Columnstore-indekser). Formålet med optimeringen er at bestille søgenøgler fra den eksterne tabel, inden der hentes data fra den interne. Således vil en tilfældig adgang være sekventiel.
Udførelsesplanen viser ikke batchsorteringsoperationen som en separat operatør. I stedet kan du se egenskaben Optimized=true i operatøren Nested Loops. Hvis det var muligt at se batchsortering som en separat operatør i planen, ville det se sådan ud:
I denne pseudoplan læser vi data fra det ikke-klyngede ix_CustomerID-indeks i rækkefølgen efter denne CustomerID-indeksnøgle. Derefter skal vi udføre nøgleopslag i det klyngede indeks, da ix_CustomerID ikke er et dækkende indeks. Nøgleopslag er en klynget indeksnøglesøgning – en tilfældig adgang. For at gøre det sekventielt kan SQL Server udføre batchsortering efter den klyngede indeksnøgle.
For at lære mere om batchsortering, se venligst min artikel Batchsortering og indlejrede løkker.
Denne optimering giver et stort løft med et tilstrækkeligt antal rækker. Du kan læse mere om dets testresultater i bloggen OPTIMIZED Nested Loops Joins, skabt af Craig Freedman, en optimeringsudvikler.
Men hvis det faktiske antal rækker er mindre end det forventede, kan ekstra CPU-omkostninger til at bygge denne slags skjule fordelene, øge CPU-forbruget og reducere dens ydeevne.
Overvej dette særlige eksempel:
use tempdb; go -- create a test table (SalesOrderID - clustered PK) create table dbo.SalesOrder(SalesOrderID int identity primary key, CustomerID int not null, SomeData char(200) not null); go -- add test data with n as (select top(1000000) rn = row_number() over(order by (select null)) from sys.all_columns c1,sys.all_columns c2) insert dbo.SalesOrder(CustomerID, SomeData) select rn%500000, str(rn,100) from n; -- create a clustered index create index ix_c on dbo.Salesorder(CustomerID); go -- the batch sort optimization is enabled by default (Nested Loops: Optimized = true) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000; -- disable it with the DISABLE_OPTIMIZED_NESTED_LOOP hint (Nested Loops: Optimized = false) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP')); go
Det returnerede resultat:
Jeg vil gerne henlede din opmærksomhed på den forskellige rækkefølge af rækker i outputtet. Serveren returnerer rækker i den rækkefølge, den behandler dem, da vi ikke eksplicit har angivet ORDER BY. I det første tilfælde læser vi gradvist fra ix_c-indekset. Men for at optimere tilfældige aflæsninger fra det klyngede indeks filtrerer vi rækker efter den klyngede SalesOrderID-indeksnøgle. I det andet tilfælde er der ingen sortering, ligesom læsninger udføres i CustomerID-nøglerækkefølgen af det ikke-klyngede indeks ix_c.
Forskel fra 2340-sporingsflaget
På trods af det faktum, at dokumentationen specificerer 2340-sporingsflaget som en ækvivalent til DISABLE_OPTIMIZED_NESTED_LOOP-hintet, er det faktisk ikke sandt.
Overvej følgende eksempel, hvor jeg vil bruge kommandoen UPDATE STATISTICS ... WITH PAGECOUNT undocumented til at bedrage optimizeren ved at sige, at tabellen tager flere sider, end den faktisk har. Så tag et kig på disse forespørgsler:
- Uden nogen tip (jeg har tilføjet MAXDOP for at holde en enkel plan);
- Med DISABLE_OPTIMIZED_NESTED_LOOP tippet;
- Med 2340-sporingsflaget.
-- create a huge table update statistics dbo.SalesOrder with pagecount = 100000; go set showplan_xml on; go -- 1. without hints select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(maxdop 1); -- 2. hint select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'), maxdop 1); -- 3. trace flag select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(querytraceon 2340, maxdop 1); go set showplan_xml off; go
Som et resultat har vi følgende planer:
Indlejrede løkker har egenskaben Optimeret =falsk i alle tre planer. Faktum er, at ved at øge bordbredden øger vi også omkostningerne til dataadgang. Når omkostningerne er høje nok, kan SQL Server bruge den eksplicitte sorteringsoperator i stedet for den implicitte batchsorteringsoperator. Vi kan se dette i den første forespørgselsplan.
I den anden forespørgsel brugte vi DISABLE_OPTIMIZED_NESTED_LOOP hint, der slår den implicitte batchsortering fra. Det fjerner dog en eksplicit sortering af en separat operatør.
I den tredje plan kan vi se, at trods tilføjelse af 2340-sporingsflaget eksisterer sorteringsoperatøren.
Forskellen mellem hintet og flaget er således som følger:et hint deaktiverer optimeringen ved at omdanne en tilfældig adgang til en seriel afhængig af, om serveren implementerer det med den implicitte batch-sortering eller med den separate sorteringsoperatør.
P.S. Planerne kan afhænge af udstyret. Derfor, hvis du ikke kan køre disse forespørgsler, kan du prøve at øge eller formindske SomeData char(200)-kolonnestørrelsen i dbo.SalesOrder tabelbeskrivelsen.
Artiklen blev oversat af Codingsight-teamet med tilladelse fra forfatteren.