sql >> Databasteknik >  >> RDS >> Database

Hur filtrerade index kan vara en mer kraftfull funktion

Missförstå mig inte; Jag älskar filtrerade index. De skapar möjligheter för mycket effektivare användning av I/O och låter oss slutligen implementera korrekta ANSI-kompatibla unika begränsningar (där mer än en NULL är tillåten). De är dock långt ifrån perfekta. Jag ville peka ut några områden där filtrerade index kan förbättras och göra dem mycket mer användbara och praktiska för en stor del av arbetsbelastningen där ute.

Först, de goda nyheterna

Filtrerade index kan göra mycket snabbt arbete med tidigare dyra frågor och göra det med mindre utrymme (och därmed minskad I/O, även när de skannas).

Ett snabbt exempel med Sales.SalesOrderDetailEnlarged (byggt med detta skript av Jonathan Kehayias (@SQLPoolBoy)). Den här tabellen har 4,8 mm rader, med 587 MB data och 363 MB index. Det finns bara en nollbar kolumn, CarrierTrackingNumber , så låt oss leka med den. Tabellen har för närvarande ungefär hälften av dessa värden (2,4MM) som NULL. Jag kommer att minska det till cirka 240K för att simulera ett scenario där en liten procentandel av raderna i tabellen faktiskt är kvalificerade för ett index, för att bäst framhäva fördelarna med ett filtrerat index. Följande fråga påverkar 2,17 mm rader och lämnar 241 507 rader med ett NULL-värde för CarrierTrackingNumber :

UPDATE Sales.SalesOrderDetailEnlarged 
    SET CarrierTrackingNumber = 'x'
      WHERE CarrierTrackingNumber IS NULL
      AND SalesOrderID % 10 <> 3;

Låt oss nu säga att det finns ett affärskrav där vi ständigt vill granska beställningar som har produkter som ännu inte har tilldelats ett spårningsnummer (tänk beställningar som delas upp och skickas separat). I den aktuella tabellen kör vi dessa frågor (och jag har lagt till DBCC-kommandon för att säkerställa kall cache i alla fall):

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 klustrade indexsökningar och ger följande körtidsmått (som fångat med SQL Sentry Plan Explorer):

På "gamla" dagar (det vill säga sedan SQL Server 2005) skulle vi ha skapat det här indexet (och faktiskt, även i SQL Server 2012, är detta indexet som SQL Server rekommenderar):

CREATE INDEX IX_NotVeryHelpful
ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber])
INCLUDE ([SalesOrderID],[ProductID]);

Med det indexet på plats och kör ovanstående frågor igen, här är mätvärdena, där båda frågorna använder en indexsökning som du kan förvänta dig:

Och sedan släppa det indexet och skapa ett något annat, helt enkelt lägga till en WHERE klausul:

CREATE INDEX IX_Filtered_CTNisNULL
ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber])
INCLUDE ([SalesOrderID],[ProductID])
WHERE CarrierTrackingNumber IS NULL;

Vi får dessa resultat, och båda frågorna använder det filtrerade indexet för sina sökningar:

Här är det extra utrymme som krävs av varje index, jämfört med minskningen av körtid och I/O för ovanstående frågor:

Index Indexutrymme Lägg till utrymme Längd Läser
Inget dedikerat index 363 MB 15 700 ms ~164 000
Icke-filtrerat index 530 MB 167 MB (+46%) 169 ms 1 084
Filtrerat index 367 MB 4 MB (+1%) 170 ms 1 084


Så, som du kan se, ger det filtrerade indexet prestandaförbättringar som är nästan identiska med det icke-filtrerade indexet (eftersom båda kan få sina data med samma antal läsningar), men med mycket lägre lagringsutrymme kostnad, eftersom det filtrerade indexet bara behöver lagra och underhålla de rader som matchar filterpredikatet.

Låt oss nu återställa tabellen till dess ursprungliga tillstånd:

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) och Michelle Ufford (@sqlfool) har gjort ett fantastiskt jobb med att beskriva prestandafördelarna med filtrerade index på sina egna sätt, och du bör också kolla in deras inlägg:

  • Michelle Ufford:Filtrerade index:Vad du behöver veta
  • Tim Chapman:The Joys of Filtered Indexes

Också ANSI-kompatibla unika begränsningar (typ av)

Jag tänkte också kort nämna ANSI-kompatibla unika begränsningar. I SQL Server 2005 skulle vi skapa en unik begränsning så här:

CREATE TABLE dbo.Personnel
(
  EmployeeID INT PRIMARY KEY,
  SSN CHAR(9) NULL,
  -- ... other columns ...
  CONSTRAINT UQ_SSN UNIQUE(SSN)
);

(Vi skulle också kunna skapa ett unikt icke-klustrat index istället för en begränsning; den underliggande implementeringen är i huvudsak densamma.)

Nu är detta inga problem om SSN är kända vid tidpunkten för inträde:

INSERT dbo.Personnel(EmployeeID, SSN)
VALUES(1,'111111111'),(2,'111111112');

Det är också bra om vi har ett tillfälligt SSN som inte är känt vid tidpunkten för inresan (tänk på en visumsökande eller kanske till och med en utländsk arbetstagare som inte har ett SSN och aldrig kommer att göra det):

INSERT dbo.Personnel(EmployeeID, SSN)
VALUES(3,NULL);

Än så länge är allt bra. Men vad händer när vi har en sekund anställd med ett okänt SSN?

INSERT dbo.Personnel(EmployeeID, SSN)
VALUES(4,NULL);

Resultat:

Msg 2627, Level 14, State 1, Line 1
Brott mot UNIQUE KEY-begränsningen 'UQ_SSN'. Kan inte infoga dubblettnyckel i objektet 'dbo.Personnel'. Dubblettnyckelvärdet är ().
Satsen har avslutats.

Så när som helst kan bara ett NULL-värde finnas i den här kolumnen. Till skillnad från de flesta scenarier är detta ett fall där SQL Server behandlar två NULL-värden som lika (istället för att fastställa att likhet helt enkelt är okänd och i sin tur falsk). Folk har klagat över denna inkonsekvens i flera år.

Om detta är ett krav kan vi nu kringgå detta med filtrerade index:

ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN;
GO
 
CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN)
  WHERE SSN IS NOT NULL;

Nu fungerar vår 4:e insättning alldeles utmärkt, eftersom unikhet endast framtvingas på icke-NULL-värden. Detta är ett slags fusk, men det uppfyller de grundläggande kraven som ANSI-standarden avsåg (även om SQL Server inte tillåter oss att använda ALTER TABLE ... ADD CONSTRAINT syntax för att skapa en filtrerad unik begränsning).

Men håll i telefonen

Det här är bra exempel på vad vi kan göra med filtrerade index, men det finns många saker vi fortfarande inte kan göra, och flera begränsningar och problem som kommer upp som ett resultat.

Statistikuppdateringar

Detta är en av de viktigare begränsningarna IMHO. Filtrerade index gynnas inte av automatisk uppdatering av statistik baserat på en procentuell förändring av den delmängd av tabellen som identifieras av filterpredikatet; den är baserad (som alla icke-filtrerade index) på churn mot hela tabellen. Det betyder att, beroende på hur stor andel av tabellen som finns i det filtrerade indexet, kan antalet rader i indexet fyrdubblas eller halveras och statistiken kommer inte att uppdateras om du inte gör det manuellt. Kimberly Tripp har gett bra information om detta (och Gail Shaw nämner ett exempel där det tog 257 000 uppdateringar innan statistiken uppdaterades för ett filtrerat index som bara innehöll 10 000 rader):

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/

Dessutom har Kimberlys kollega, Joe Sack (@JosephSack), skickat in ett Connect-objekt som föreslår att man korrigerar detta beteende för både filtrerade index och filtrerad statistik.

Begränsningar för filteruttryck

Det finns flera konstruktioner som du inte kan använda i ett filterpredikat, till exempel NOT IN , OR och dynamiska/icke-deterministiska predikat som WHERE col >= DATEADD(DAY, -1, GETDATE()) . Dessutom kanske optimeraren inte känner igen ett filtrerat index om predikatet inte exakt matchar WHERE klausul i indexdefinitionen. Här är några Connect-objekt som försöker locka till sig lite stöd för bättre täckning här:

Filtrerat index tillåter inte filter på disjunktioner (stängd:av design)
Skapandet av filtrerat index misslyckades med NOT IN-satsen (stängd:av design)
Stöd för mer komplex WHERE-sats i filtrerade index (aktiv)

Andra potentiella användningsområden är för närvarande inte möjliga

Vi kan för närvarande inte skapa ett filtrerat index på en beständig beräknad kolumn, även om den är deterministisk. Vi kan inte peka en främmande nyckel mot ett unikt filtrerat index; om vi vill att ett index ska stödja den främmande nyckeln utöver de frågor som stöds av det filtrerade indexet, måste vi skapa ett andra, redundant, icke-filtrerat index. Och här är några andra liknande begränsningar som antingen har förbisetts eller inte har beaktats ännu:

Bör vara möjligt att skapa ett filtrerat index på en deterministisk beständig beräknad kolumn (aktiv)
Tillåt filtrerat unikt index att vara en kandidatnyckel för en främmande nyckel (aktiv)
möjlighet att skapa filterindex på indexerade vyer (stängd:fixar inte)
Partitioneringsfel 1908 – Förbättra partitionering (stängd:fixar inte)
SKAPA "FILTERAT" COLUMNSTORE INDEX (aktiv)

Problem med MERGE

Och MERGE gör ännu ett framträdande på min "se upp"-lista:

MERGE utvärderar filtrerat index per rad, inte efter operation, vilket orsakar filtrerat indexöverträdelse (stängd:fixar inte)
MERGE kan inte uppdateras med filtrerat index på plats (stängd:fixad)
MERGE-satsfel när INSERT/DELETE används och filtrerat index (aktiv)
MERGE rapporterar felaktigt unika nyckelöverträdelser (aktiv)


Medan en av dessa (till synes närbesläktade) buggar säger att det är fixat i SQL Server 2012, kan du behöva kontakta PSS om du stöter på någon variant av det här problemet, särskilt på tidigare versioner (eller sluta använda MERGE , som jag har föreslagit tidigare).

Begränsningar för verktyg / DMV / inbyggda

Det finns många DMV, DBCC-kommandon, systemprocedurer och klientverktyg som vi börjar lita på med tiden. Men alla dessa saker uppdateras inte för att dra nytta av nya funktioner; filtrerade index är inget undantag. Följande Connect-objekt pekar på några problem som kan göra dig snurrig om du förväntar dig att de ska fungera med filtrerade index:

Det finns inget sätt att skapa ett filtrerat index från SSMS när man designar en ny tabell (stängd:fixar inte)
Filteruttrycket för ett filtrerat index går förlorat när en tabell modifieras av tabelldesignern (stängd:fixar inte)
Tabelldesignern skriver inte WHERE-satsen i filtrerade index (aktiv)
SSMS-tabelldesignern bevarar inte indexfilteruttryck vid tabellombyggnad (stängd:fixar inte)
DBCC PAGE felaktig utdata med filtrerade index (aktiv)
SQL 2008 filtrerade indexförslag från DM Views och DTA (stängd:fixar inte)
Förbättringar av de saknade indexen DMV för filtrerade index (stängd:fixar inte)
Syntaxfel vid replikering av komprimerade filtrerade index (stängd:fixar inte)
Agent:jobb använder icke-standardalternativ när ett T-SQL-skript körs (stängd:fixar inte)
Visa beroenden misslyckas med Transact-SQL Error 515 (aktiv)
Visningsberoenden misslyckas på vissa objekt (stängd:fixar inte)
Skillnader i indexalternativ upptäcks inte i schemajämförelsen för två databaser (stängd:extern)
Föreslå exponering av indexfiltervillkor i alla vyer av indexinformation (stängd:fixar inte)
sp_helpIndex-resultat bör inkludera filteruttrycket för filterindex (aktiv)
Överbelasta sp_help, sp_columns, sp_helpindex för 2008 års funktioner (stängd:fixar inte)


För de tre sista, håll inte andan – Microsoft kommer sannolikt inte att investera någon gång i sp_-procedurerna, DMV:er, INFORMATION_SCHEMA-vyer etc. Se istället Kimberly Tripps sp_helpindex-omskrivningar, som inkluderar information om filtrerade index längs med andra nya funktioner som Microsoft har lämnat bakom sig.

Optimerarens begränsningar

Det finns flera Connect-objekt som beskriver fall där filtrerade index *kan* användas av optimeraren, men istället ignoreras. I vissa fall betraktas dessa inte som "buggar" utan snarare "luckor i funktionalitet"...

SQL använder inte filtrerat index på en enkel fråga (stängd:av design)
Utförandeplanen för filtrerat index är inte optimerad (stängd:fixar inte)
Filtrerat index används inte och nyckelsökning utan utdata (stängd:fixar inte)
Användning av filtrerat index på BIT-kolumn beror på exakta SQL-uttryck som används i WHERE-satsen (aktiv)
Länkad serverfråga optimerar inte korrekt när ett filtrerat unikt index finns (stängd:fixar inte)
Row_Number() ger oförutsägbara resultat över länkade servrar där filtrerade index används (stängd:ingen repro)
Uppenbart filtrerat index som inte används av QP (stängd:av design)
Känn igen unika filtrerade index som unika (aktiv)


Paul White (@SQL_Kiwi) postade nyligen här på SQLPerformance.com ett inlägg som går in i detalj om ett par av ovanstående optimeringsbegränsningar.

Och Tim Chapman skrev ett bra inlägg som beskriver några andra begränsningar för filtrerade index – som oförmågan att matcha predikatet med en lokal variabel (fixad i 2008 R2 SP1) och oförmågan att specificera ett filtrerat index i en indextips.

Slutsats

Filtrerade index har stor potential och jag hade extremt höga förhoppningar på dem när de först introducerades i SQL Server 2008. Men de flesta begränsningarna som levererades med deras första version existerar fortfarande idag, en och en halv (eller två, beroende på din perspektiv) större utgåvor senare. Ovanstående verkar vara en ganska omfattande tvättlista med saker som måste åtgärdas, men jag menade inte att det skulle komma fram på det sättet. Jag vill bara att folk ska vara medvetna om det stora antalet potentiella problem de kan behöva tänka på när de utnyttjar filtrerade index.


  1. Använder aktuell tid i UTC som standardvärde i PostgreSQL

  2. Spåra PostgreSQL med perf

  3. Använda Docker på Azure Container Service med Swarm Cluster

  4. Kontrollera statistikmål i PostgreSQL