sql >> Databasteknik >  >> RDS >> Database

Filtrerade index och INKLUDERADE kolumner

Filtrerade index är otroligt kraftfulla, men jag ser fortfarande viss förvirring där ute – särskilt om kolumnerna som används i filtren och vad som händer när du vill dra åt filtren.

En ny fråga på dba.stackexchange bad om hjälp om varför kolumner som används i filtret för ett filtrerat index ska inkluderas i indexets "inkluderade" kolumner. Utmärkt fråga – förutom att jag kände att det började på en dålig premiss, eftersom de kolumnerna inte borde behöva ingå i indexet . Ja de hjälper, men inte på det sätt som frågan tycktes antyda.

För att du inte ska titta på själva frågan, här är en snabb sammanfattning:

För att tillfredsställa denna fråga...

SELECT Id, DisplayName 
FROM Users 
WHERE Reputation > 400000;

…följande filtrerade index är ganska bra:

CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club
ON dbo.Users ( DisplayName, Id )
INCLUDE ( Reputation )
WHERE Reputation > 400000;

Men trots att det här indexet är på plats rekommenderar frågeoptimeraren följande index om det filtrerade värdet skärps till exempelvis 450 000.

CREATE NONCLUSTERED INDEX IndexThatWasMissing
ON dbo.Users ( Reputation )
INCLUDE ( DisplayName, Id );

Jag parafraserar frågan lite här, som börjar med att hänvisa till den här situationen och sedan bygger ett annat exempel, men tanken är densamma. Jag ville bara inte göra saker mer komplicerade genom att involvera en separat tabell.

Punkten är – indexet som föreslås av QO är det ursprungliga indexet men vänt på huvudet. Det ursprungliga indexet hade rykte i INCLUDE-listan och DisplayName och Id som nyckelkolumner, medan det nya rekommenderade indexet är tvärtom med Reputation som nyckelkolumn och DisplayName &ID i INCLUDE. Låt oss titta närmare på varför.

Frågan hänvisar till ett inlägg av Erik Darling, där han förklarar att han ställde in "450 000"-frågan ovan genom att sätta rykte i kolumnen INKLUDERA. Erik visar att utan Reputation i INCLUDE-listan behöver en fråga som filtrerar till ett högre värde av Reputation göra Lookups (dåligt!), eller kanske till och med ge upp helt och hållet på det filtrerade indexet (potentiellt ännu värre). Han drar slutsatsen att med ryktekolumnen i INCLUDE-listan kan SQL ha statistik, så att den kan göra bättre val, och visar att med Reputation i INCLUDE en mängd olika frågor som alla filtrerar på högre ryktevärden alla genomsöker hans filtrerade index.

I ett svar på dba.stackexchange-frågan påpekar Brent Ozar att Eriks förbättringar inte är särskilt stora eftersom de orsakar skanningar. Jag återkommer till det, för det är en intressant punkt i sig och något felaktig.

Låt oss först tänka lite på index i allmänhet.

Ett index ger en ordnad struktur till en uppsättning data. (Jag skulle kunna vara pedantisk och påpeka att att läsa igenom data i ett index från början till slut kan hoppa dig från sida till sida på ett till synes slumpartat sätt, men ändå när du läser igenom sidorna, följer du pekarna från en sida till nästa kan du vara säker på att data är ordnade. Inom varje sida kan du till och med hoppa runt för att läsa data i ordning, men det finns en lista som visar vilka delar (slots) av sidan som ska läsas i vilken ordning. Det är ingen mening med mitt pedanteri förutom att svara de lika pedantiska som kommer att kommentera om jag inte gör det.)

Och den här ordningen är enligt nyckelkolumnerna – det är den enkla biten som alla får. Det är användbart inte bara för att kunna undvika att ändra ordning på data senare, utan också för att snabbt kunna hitta en viss rad eller radintervall efter dessa kolumner.

Indexets bladnivåer innehåller värdena i alla kolumner i INCLUDE-listan, eller i fallet med ett Clustered Index, värdena över alla kolumner i tabellen (förutom icke-beständiga beräknade kolumner). De andra nivåerna i indexet innehåller bara nyckelkolumnerna och (om indexet inte är unikt) den unika adressen för raden – som antingen är nycklarna till det klustrade indexet (med radens uniquiifier om det klustrade indexet inte heller är unikt ) eller RowID-värdet för en heap, tillräckligt för att ge enkel åtkomst till alla andra kolumnvärden för raden. Bladnivåerna inkluderar också all "adress"-information.

Men det är inte det intressanta för det här inlägget. Det intressanta med det här inlägget är vad jag menar med "till en uppsättning data". Kom ihåg att jag sa "Ett index ger en ordnad struktur till en uppsättning data ".

I ett klustrat index är den datauppsättningen hela tabellen, men det kan vara något annat. Du kan förmodligen redan föreställa dig hur de flesta icke-klustrade index inte involverar alla kolumner i tabellen. Detta är en av de saker som gör icke-klustrade index så användbara, eftersom de vanligtvis är mycket mindre än den underliggande tabellen.

När det gäller en indexerad vy, kan vår uppsättning data vara resultatet av en hel fråga, inklusive sammanfogningar över många tabeller! Det är för ett annat inlägg.

Men i ett filtrerat index är det inte bara en kopia av en delmängd av kolumner, utan också en delmängd av rader. Så i exemplet här är indexet bara för användare med mer än 400 000 rykte.

CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club_NoInclude
ON dbo.Users ( DisplayName, Id )
WHERE Reputation > 400000;

Detta index tar användare som har mer än 400 000 rykte och beställer dem efter DisplayName och Id. Det kan vara unikt eftersom (förmodligen) Id-kolumnen redan är unik. Om du provar något liknande på ditt eget bord kan du behöva vara försiktig med det.

Men vid det här laget bryr sig indexet inte om vad ryktet är för varje användare – det bryr sig bara om ryktet är tillräckligt högt för att vara med i indexet eller inte. Om en användares rykte uppdateras och det tippar över tröskeln kommer användarens DisplayName och ID att infogas i indexet. Om den sjunker under kommer den att raderas från indexet. Det är precis som att ha ett separat bord för storspelare, förutom att vi får in folk i det bordet genom att öka deras ryktevärde över tröskeln på 400k i den underliggande tabellen. Den kan göra detta utan att faktiskt behöva lagra själva ryktevärdet.

Så nu om vi vill hitta personer som har en tröskel över 450 000, saknar det indexet viss information.

Visst, vi kan med säkerhet säga att alla vi hittar finns i det indexet – men indexet innehåller inte tillräckligt med information i sig för att filtrera vidare på Reputation. Om jag berättade att jag hade en alfabetisk lista över Oscarsvinnande filmer för bästa film från 1990-talet (American Beauty, Braveheart, Dances With Wolves, English Patient, Forrest Gump, Schindler's List, Shakespeare in Love, Silence of the Lambs, Titanic, Unforgiven) , då kan jag försäkra er att vinnarna för 1994-1996 skulle vara en delmängd av dessa, men jag kan inte svara på frågan utan att först få lite mer information.

Uppenbarligen skulle mitt filtrerade index vara mer användbart om jag hade inkluderat årtalet, och potentiellt ännu mer om året var en nyckelkolumn, eftersom min nya fråga vill hitta de för 1994-1996. Men jag designade förmodligen det här indexet kring en fråga för att lista alla filmer från 1990-talet i alfabetisk ordning. Den frågan bryr sig inte om vad det faktiska året är, bara om det är på 1990-talet eller inte, och jag behöver inte ens returnera årtalet – bara titeln – så jag kan skanna mitt filtrerade index för att få resultaten. För den frågan behöver jag inte ens ordna om resultaten eller hitta startpunkten – mitt index är verkligen perfekt.

Ett mer praktiskt exempel på att inte bry sig om värdet på kolumnen i filtret är på status, som:

WHERE IsActive = 1

Jag ser ofta kod som flyttar data från en tabell till en annan när rader slutar vara "aktiva". Människor vill inte att gamla rader ska belamra deras tabell, och de inser att deras "heta" data bara är en liten delmängd av all deras data. Så de flyttar bort sina kyldata till en arkivtabell och håller deras Active-tabell liten.

Ett filtrerat index kan göra detta åt dig. Bakom kulisserna. Så fort du uppdaterar raden och ändrar den där IsActive-kolumnen till något annat än 1. Om du bara bryr dig om att ha aktiv data i de flesta av dina index, är filtrerade index idealiska. Det kommer till och med att ta tillbaka rader till indexen om IsActive-värdet ändras tillbaka till 1.

Men du behöver inte sätta IsActive i INCLUDE-listan för att uppnå detta. Varför skulle du vilja lagra värdet – du vet redan vad värdet är – det är 1! Om du inte ber om att returnera värdet borde du inte behöva det. Och varför skulle du returnera värdet när du redan vet att svaret är 1, eller hur?! Förutom att frustrerande nog kommer statistiken som Erik hänvisar till i sitt inlägg att dra fördel av att vara med i INKLUDERA-listan. Du behöver det inte för frågan, men du bör inkludera det för statistiken.

Låt oss fundera på vad frågeoptimeraren behöver göra för att ta reda på användbarheten av ett index.

Innan det kan göra mycket alls måste det överväga om indexet är en kandidat. Ingen idé att använda ett index om det inte har alla rader som kan behövas – inte om vi inte har ett effektivt sätt att få resten. Om jag vill ha filmer från 1985-1995 så är mitt index över 1990-talsfilmer ganska meningslöst. Men för 1994-1996 är det kanske inte illa.

Vid det här tillfället, precis som alla indexöverväganden, måste jag fundera på om det kommer att hjälpa tillräckligt för att hitta data och få den i en ordning som kommer att hjälpa till att exekvera resten av frågan (möjligen för en Merge Join, Stream Aggregate, tillfredsställande en BESTÄLLNING AV, eller olika andra skäl). Om mitt frågefilter matchar indexfiltret exakt, behöver jag inte filtrera mer – det räcker med att använda indexet. Det här låter bra, men om det inte stämmer exakt, om mitt frågefilter är snävare än indexfiltret (som mitt exempel från 1994-1996, eller Eriks 450 000), kommer jag att behöva ha dessa årsvärden eller ryktevärden att kontrollera – förhoppningsvis få dem antingen från INKLUDERADE på bladnivå eller någonstans i mina nyckelkolumner. Om de inte finns i indexet måste jag göra en uppslagning för varje rad i mitt filtrerade index (och helst ha en uppfattning om hur många gånger min uppslagning kommer att anropas, vilket är statistiken som Erik vill ha kolumnen ingår för).

Helst är alla index jag planerar att använda korrekt ordnade (via nycklarna), INNEHÅLLER alla kolumner jag behöver returnera och är förfiltrerade till bara de rader jag behöver. Det skulle vara det perfekta indexet, och min genomförandeplan kommer att vara en skanning.

Det stämmer, en SCAN. Inte en sökning, utan en skanning. Det börjar på första sidan i mitt index och fortsätter att ge mig rader tills jag har så många som jag behöver, eller tills det inte finns fler rader att returnera. Att inte hoppa över några, inte sortera dem – bara ge mig raderna i ordning.

En sökning skulle tyda på att jag inte behöver hela indexet, vilket innebär att jag slösar resurser på att underhålla den delen av indexet, och för att fråga den måste jag hitta startpunkten och fortsätta kontrollera raderna för att se om jag har träffa slutet eller inte. Om min skanning har ett predikat, så måste jag se igenom (och testa) mer data än jag behöver, men om mina indexfilter är perfekta bör frågeoptimeraren känna igen det och inte behöva utföra dessa kontroller .

Sluta tankar

INCLUDEs är inte kritiska för filtrerade index. De är användbara för att ge enkel åtkomst till kolumner som kan vara användbara för din fråga, och om du råkar skärpa vad som finns i ditt filtrerade index med någon kolumn, oavsett om det nämns i filtret eller inte, bör du överväga att ha den kolumnen i mixen. Men vid det tillfället borde du fråga dig om ditt indexfilter är det rätta, vad du mer bör ha i din INKLUDERA-lista, och till och med vad nyckelkolumnerna ska vara. Eriks frågor spelade inte bra eftersom han behövde information som inte fanns i indexet, trots att han hade nämnt kolumnen i filtret. Han fann en bra användning för statistiken också, och jag skulle fortfarande uppmuntra dig att ta med filterkolumnerna av den anledningen. Men att lägga in dem i en INCLUDE tillåter inte att de plötsligt börjar göra en sökning, eftersom det inte är så något index fungerar, vare sig det är filtrerat eller inte.

Jag vill att du, läsare, ska förstå filtrerade index riktigt bra. De är otroligt användbara och, när du börjar föreställa dem som tabeller i sina egna rättigheter, kan de bli en del av din övergripande databasdesign. De är också en anledning till att alltid använda inställningarna ANSI_NULLs och QUOTED_IDENTIFIER, eftersom du kommer att få fel från filtrerat index om inte dessa inställningar är PÅ, men förhoppningsvis ser du redan till att de alltid är på ändå.

Åh, och de filmerna var Forrest Gump, Braveheart och The English Patient.

@rob_farley


  1. SSIS källformat Implicit konvertering för datum och tid

  2. Som skiftlägeskänslig i MySQL

  3. Pivotera, avpivotera och dela kolumner i Power BI Query Editor

  4. fel vid installation av psycopg2, bibliotek hittades inte för -lssl