Misforstå mig ikke; Jeg elsker filtrerede indekser. De skaber muligheder for meget mere effektiv brug af I/O og giver os endelig mulighed for at implementere ordentlige ANSI-kompatible unikke begrænsninger (hvor mere end én NULL er tilladt). De er dog langt fra perfekte. Jeg ville pege på et par områder, hvor filtrerede indekser kunne forbedres og gøre dem meget mere nyttige og praktiske til en stor del af arbejdsbelastninger derude.
For det første den gode nyhed
Filtrerede indekser kan gøre meget hurtigt arbejde med tidligere dyre forespørgsler og gøre det ved at bruge mindre plads (og dermed reduceret I/O, selv når de scannes).
Et hurtigt eksempel med Sales.SalesOrderDetailEnlarged
(bygget ved hjælp af dette script af Jonathan Kehayias (@SQLPoolBoy)). Denne tabel har 4,8 mm rækker med 587 MB data og 363 MB indekser. Der er kun én nullbar kolonne, CarrierTrackingNumber
, så lad os lege med den. Som den er, har tabellen i øjeblikket omkring halvdelen af disse værdier (2,4MM) som NULL. Jeg vil reducere det til omkring 240K for at simulere et scenario, hvor en lille procentdel af rækkerne i tabellen faktisk er berettiget til et indeks, for bedst at fremhæve fordelene ved et filtreret indeks. Følgende forespørgsel påvirker 2,17 mm rækker, hvilket efterlader 241.507 rækker med en NULL-værdi for CarrierTrackingNumber
:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = 'x' WHERE CarrierTrackingNumber IS NULL AND SalesOrderID % 10 <> 3;
Lad os nu sige, at der er et forretningskrav, hvor vi konstant ønsker at gennemgå ordrer, der har produkter, der endnu ikke er tildelt et sporingsnummer (tænk på ordrer, der er delt op og sendt separat). På den aktuelle tabel ville vi køre disse forespørgsler (og jeg har tilføjet DBCC-kommandoer for at sikre kold cache i alle tilfælde):
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Som kræver klyngede indeksscanninger og giver følgende runtime-metrics (som fanget med SQL Sentry Plan Explorer):
I "gamle" dage (det vil sige siden SQL Server 2005), ville vi have oprettet dette indeks (og faktisk, selv i SQL Server 2012, er dette det indeks, SQL Server anbefaler):
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
Med det indeks på plads og køre ovenstående forespørgsler igen, her er metrics, hvor begge forespørgsler bruger en indekssøgning, som du kunne forvente:
Og så droppe det indeks og oprette et lidt anderledes, blot tilføje en WHERE
klausul:
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Vi får disse resultater, og begge forespørgsler bruger det filtrerede indeks til deres søgninger:
Her er den ekstra plads, der kræves af hvert indeks, sammenlignet med reduktionen i runtime og I/O for ovenstående forespørgsler:
Indeks | Indeksplads | Tilføjet plads | Varighed | Læser |
---|---|---|---|---|
Intet dedikeret indeks | 363 MB | 15.700 ms | ~164.000 | |
Ikke-filtreret indeks | 530 MB | 167 MB (+46%) | 169 ms | 1.084 |
Filtreret indeks | 367 MB | 4 MB (+1%) | 170 ms | 1.084 |
Så, som du kan se, leverer det filtrerede indeks ydeevneforbedringer, der er næsten identiske med det ikke-filtrerede indeks (da begge er i stand til at få deres data ved at bruge det samme antal læsninger), men med meget lavere lagerplads pris, da det filtrerede indeks kun skal gemme og vedligeholde de rækker, der matcher filterprædikatet.
Lad os nu sætte tabellen tilbage til dens oprindelige tilstand:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = NULL WHERE CarrierTrackingNumber = 'x'; DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged; DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;
Tim Chapman (@chapmandew) og Michelle Ufford (@sqlfool) har gjort et fantastisk stykke arbejde med at skitsere ydeevnefordelene ved filtrerede indekser på deres egne måder, og du bør også tjekke deres indlæg ud:
- Michelle Ufford:Filtrerede indekser:Hvad du behøver at vide
- Tim Chapman:Glæderne ved filtrerede indekser
Også ANSI-kompatible unikke begrænsninger (en slags)
Jeg tænkte, at jeg også kort ville nævne ANSI-kompatible unikke begrænsninger. I SQL Server 2005 ville vi skabe en unik begrænsning som denne:
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Vi kunne også skabe et unikt ikke-klynget indeks i stedet for en begrænsning; den underliggende implementering er stort set den samme.)
Nu er dette ikke noget problem, hvis SSN'er er kendt på tidspunktet for indtastning:
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
Det er også fint, hvis vi lejlighedsvis har et SSN, som ikke er kendt på tidspunktet for indrejsen (tænk på en visumansøger eller måske endda en udenlandsk arbejdstager, der ikke har et SSN og aldrig vil):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
Så langt så godt. Men hvad sker der, når vi har et sekund medarbejder med et ukendt SSN?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Resultat:
Msg 2627, Level 14, State 1, Line 1Overtrædelse af UNIQUE KEY constraint 'UQ_SSN'. Kan ikke indsætte dubletnøgle i objektet 'dbo.Personnel'. Dubletnøgleværdien er (
Sætningen er blevet afsluttet.
Så til enhver tid kan der kun eksistere én NULL-værdi i denne kolonne. I modsætning til de fleste scenarier er dette et tilfælde, hvor SQL Server behandler to NULL-værdier som lige (i stedet for at bestemme, at lighed simpelthen er ukendt og til gengæld falsk). Folk har klaget over denne inkonsekvens i årevis.
Hvis dette er et krav, kan vi nu omgå dette ved hjælp af filtrerede indekser:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Nu fungerer vores 4. insert fint, da unikhed kun håndhæves på de ikke-NULL-værdier. Dette er en slags snyd, men det opfylder de grundlæggende krav, som ANSI-standarden havde til hensigt (selvom SQL Server ikke tillader os at bruge ALTER TABLE ... ADD CONSTRAINT
syntaks for at skabe en filtreret unik begrænsning).
Men hold telefonen
Dette er gode eksempler på, hvad vi kan gøre med filtrerede indekser, men der er mange ting, vi stadig ikke kan gøre, og flere begrænsninger og problemer, der dukker op som et resultat.
Statistikopdateringer
Dette er en af de vigtigere begrænsninger IMHO. Filtrerede indekser drager ikke fordel af automatisk opdatering af statistik baseret på en procentvis ændring af undergruppen af tabellen, der identificeres af filterprædikatet; den er baseret (som alle ikke-filtrerede indekser) på churn mod hele tabellen. Det betyder, at afhængigt af hvor stor en procentdel af tabellen der er i det filtrerede indeks, kan antallet af rækker i indekset firdobles eller halveres, og statistikken opdateres ikke, medmindre du gør det manuelt. Kimberly Tripp har givet nogle gode oplysninger om dette (og Gail Shaw nævner et eksempel, hvor det tog 257.000 opdateringer, før statistikker blev opdateret for et filtreret indeks, der kun indeholdt 10.000 rækker):
http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://www.sqlskills.com/ blogs/kimberly/category/filtered-indexes/
Kimberlys kollega, Joe Sack (@JosephSack), har også indsendt et Connect-element, der foreslår at korrigere denne adfærd for både filtrerede indekser og filtrerede statistikker.
Filter udtryksbegrænsninger
Der er flere konstruktioner, du ikke kan bruge i et filterprædikat, såsom NOT IN
, OR
og dynamiske/ikke-deterministiske prædikater som WHERE col >= DATEADD(DAY, -1, GETDATE())
. Desuden genkender optimeringsværktøjet muligvis ikke et filtreret indeks, hvis prædikatet ikke nøjagtigt matcher WHERE
klausul i indeksdefinitionen. Her er et par Connect-elementer, der prøver at lokke noget støtte til bedre dækning her:
Filtreret indeks tillader ikke filtre på disjunktioner | (lukket:af design) |
Oprettelse af filtreret indeks mislykkedes med NOT IN-sætning | (lukket:af design) |
Understøttelse af mere kompleks WHERE-sætning i filtrerede indekser | (aktiv) |
Andre potentielle anvendelser er i øjeblikket ikke mulige
Vi kan i øjeblikket ikke oprette et filtreret indeks på en vedvarende beregnet kolonne, selvom det er deterministisk. Vi kan ikke pege en fremmednøgle mod et unikt filtreret indeks; hvis vi ønsker, at et indeks skal understøtte fremmednøglen ud over de forespørgsler, der understøttes af det filtrerede indeks, skal vi oprette et andet, redundant, ikke-filtreret indeks. Og her er et par andre lignende begrænsninger, som enten er blevet overset eller ikke overvejet endnu:
Bør være muligt at oprette et filtreret indeks på en deterministisk vedvarende beregnet kolonne | (aktiv) |
Tillad filtreret unikt indeks at være en kandidatnøgle til en fremmednøgle | (aktiv) |
mulighed for at oprette filterindekser på indekserede visninger | (lukket:løses ikke) |
Partitioneringsfejl 1908 – Forbedret partitionering | (lukket:løses ikke) |
OPRET "FILTERET" KOLUMNEBUTIKINDEKS | (aktiv) |
Problemer med MERGE
Og MERGE
optræder endnu en gang på min "pas på"-liste:
FLET evaluerer filtreret indeks pr. række, ikke post-operation, hvilket forårsager overtrædelse af filtreret indeks | (lukket:løses ikke) |
MERGE kan ikke opdateres med filtreret indeks på plads | (lukket:fast) |
MERGE statement-fejl, når INSERT/DELETE bruges og filtreret indeks | (aktiv) |
FLET Rapporterer forkerte unikke nøgleovertrædelser | (aktiv) |
Mens en af disse (tilsyneladende nært beslægtede) fejl siger, at den er rettet i SQL Server 2012, er du muligvis nødt til at kontakte PSS, hvis du støder på en variation af dette problem, især på tidligere versioner (eller stoppe med at bruge MERGE , som jeg har foreslået før).
Værktøj / DMV / indbyggede begrænsninger
Der er mange DMV'er, DBCC-kommandoer, systemprocedurer og klientværktøjer, som vi begynder at stole på med tiden. Det er dog ikke alle disse ting, der er opdateret for at drage fordel af nye funktioner; filtrerede indekser er ingen undtagelse. Følgende Connect-elementer påpeger nogle problemer, der kan gøre dig gal, hvis du forventer, at de skal fungere med filtrerede indekser:
Der er ingen måde at oprette filtreret indeks fra SSMS, mens du designer en ny tabel | (lukket:løses ikke) |
Filterudtrykket for et filtreret indeks går tabt, når en tabel ændres af tabeldesigneren | (lukket:løses ikke) |
Tabeldesigner scripter ikke WHERE-sætning i filtrerede indekser | (aktiv) |
SSMS-tabeldesigner bevarer ikke indeksfilterudtryk ved tabelgenopbygning | (lukket:løses ikke) |
DBCC PAGE forkert output med filtrerede indekser | (aktiv) |
SQL 2008 filtrerede indeksforslag fra DM-visninger og DTA | (lukket:løses ikke) |
Forbedringer af de manglende indekser DMV'er for filtrerede indekser | (lukket:løses ikke) |
Syntaksfejl ved replikering af komprimerede filtrerede indekser | (lukket:løses ikke) |
Agent:job bruger ikke-standardindstillinger, når de kører et T-SQL-script | (lukket:løses ikke) |
Visningsafhængigheder mislykkes med Transact-SQL-fejl 515 | (aktiv) |
Visningsafhængigheder mislykkes på visse objekter | (lukket:løses ikke) |
Forskelle i indeksindstillinger er ikke fundet i skemasammenligningen for to databaser | (lukket:ekstern) |
Foreslå eksponering af indeksfiltertilstand i alle visninger af indeksoplysninger | (lukket:løses ikke) |
sp_helpIndex-resultater bør inkludere filterudtrykket for filterindekser | (aktiv) |
Overload sp_help, sp_columns, sp_helpindex for 2008-funktioner | (lukket:løses ikke) |
For de sidste tre, hold ikke vejret – det er usandsynligt, at Microsoft på noget tidspunkt investerer i sp_-procedurerne, DMV'er, INFORMATION_SCHEMA-visninger osv. Se i stedet Kimberly Tripps sp_helpindex-omskrivninger, som inkluderer information om filtrerede indekser langs med andre nye funktioner, som Microsoft har efterladt.
Optimeringsværktøjets begrænsninger
Der er flere Connect-elementer, der beskriver tilfælde, hvor filtrerede indekser *kunne* bruges af optimeringsværktøjet, men i stedet ignoreres. I nogle tilfælde betragtes disse ikke som "bugs", men snarere "mangler i funktionalitet"...
SQL bruger ikke filtreret indeks på en simpel forespørgsel | (lukket:af design) |
Plan for udførelse af filtreret indeks er ikke optimeret | (lukket:løses ikke) |
Filtreret indeks ikke brugt og nøgleopslag uden output | (lukket:løses ikke) |
Brug af filtreret indeks på BIT-kolonne afhænger af det nøjagtige SQL-udtryk, der bruges i WHERE-sætningen | (aktiv) |
Linket serverforespørgsel optimerer ikke korrekt, når der findes et filtreret unikt indeks | (lukket:løses ikke) |
Row_Number() giver uforudsigelige resultater over sammenkædede servere, hvor der bruges filtrerede indekser | (lukket:ingen repro) |
Tydeligt filtreret indeks ikke brugt af QP | (lukket:af design) |
Genkend unikke filtrerede indekser som unikke | (aktiv) |
Paul White (@SQL_Kiwi) postede for nylig her på SQLPerformance.com et indlæg, der går i detaljer om et par af ovenstående optimeringsbegrænsninger.
Og Tim Chapman skrev et godt indlæg, der skitserede nogle andre begrænsninger af filtrerede indekser – såsom manglende evne til at matche prædikatet til en lokal variabel (fastsat i 2008 R2 SP1) og manglende evne til at specificere et filtreret indeks i et indekstip.
Konklusion
Filtrerede indekser har et stort potentiale, og jeg havde ekstremt store forhåbninger til dem, da de først blev introduceret i SQL Server 2008. De fleste af de begrænsninger, der fulgte med deres første version, eksisterer dog stadig i dag, halvanden (eller to, afhængigt af din perspektiv) større udgivelser senere. Ovenstående virker som en ret omfattende vasketøjsliste over ting, der skal løses, men jeg mente ikke, at det skulle komme over på den måde. Jeg vil bare have, at folk skal være opmærksomme på det store antal potentielle problemer, de muligvis skal overveje, når de udnytter filtrerede indekser.