Versteh mich nicht falsch; Ich liebe gefilterte Indizes. Sie schaffen Möglichkeiten für eine viel effizientere Nutzung von I/O und ermöglichen es uns schließlich, ordnungsgemäße ANSI-konforme Unique Constraints zu implementieren (wobei mehr als eine NULL zulässig ist). Sie sind jedoch alles andere als perfekt. Ich wollte auf einige Bereiche hinweisen, in denen gefilterte Indizes verbessert werden könnten und sie für einen großen Teil der Arbeitslasten da draußen viel nützlicher und praktischer machen könnten.
Zunächst die gute Nachricht
Gefilterte Indizes können zuvor teure Abfragen sehr schnell erledigen und verbrauchen dabei weniger Platz (und damit weniger E/A, selbst wenn sie gescannt werden).
Ein kurzes Beispiel mit Sales.SalesOrderDetailEnlarged
(erstellt mit diesem Skript von Jonathan Kehayias (@SQLPoolBoy)). Diese Tabelle hat 4,8 Millionen Zeilen mit 587 MB an Daten und 363 MB an Indizes. Es gibt nur eine Nullable-Spalte, CarrierTrackingNumber
, also lass uns damit spielen. Derzeit weist die Tabelle etwa die Hälfte dieser Werte (2,4 MM) als NULL auf. Ich werde das auf etwa 240 KB reduzieren, um ein Szenario zu simulieren, in dem ein kleiner Prozentsatz der Zeilen in der Tabelle tatsächlich für einen Index geeignet ist, um die Vorteile eines gefilterten Index am besten hervorzuheben. Die folgende Abfrage betrifft 2,17 Millionen Zeilen, sodass 241.507 Zeilen mit einem NULL-Wert für CarrierTrackingNumber
verbleiben :
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = 'x' WHERE CarrierTrackingNumber IS NULL AND SalesOrderID % 10 <> 3;
Nehmen wir nun an, es gibt eine Geschäftsanforderung, bei der wir ständig Bestellungen mit Produkten überprüfen möchten, denen noch eine Tracking-Nummer zugewiesen werden muss (denken Sie an Bestellungen, die aufgeteilt und separat versendet werden). Auf der aktuellen Tabelle würden wir diese Abfragen ausführen (und ich habe die DBCC-Befehle hinzugefügt, um in jedem Fall einen Cold-Cache sicherzustellen):
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Die Clustered-Index-Scans erfordern und die folgenden Laufzeitmetriken liefern (wie mit SQL Sentry Plan Explorer erfasst):
In den "alten" Tagen (d. h. seit SQL Server 2005) hätten wir diesen Index erstellt (und tatsächlich ist dies sogar in SQL Server 2012 der Index, den SQL Server empfiehlt):
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
Nachdem dieser Index vorhanden ist und die obigen Abfragen erneut ausgeführt werden, sind hier die Metriken, wobei beide Abfragen erwartungsgemäß eine Indexsuche verwenden:
Und dann diesen Index löschen und einen etwas anderen erstellen, einfach ein WHERE
hinzufügen Klausel:
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Wir erhalten diese Ergebnisse, und beide Abfragen verwenden den gefilterten Index für ihre Suchvorgänge:
Hier ist der zusätzliche Speicherplatz, der von jedem Index benötigt wird, verglichen mit der Reduzierung der Laufzeit und der E/A der obigen Abfragen:
Index | Indexplatz | Leerzeichen hinzugefügt | Dauer | Liest |
---|---|---|---|---|
Kein dedizierter Index | 363 MB | 15.700 ms | ~164.000 | |
Ungefilterter Index | 530 MB | 167 MB (+46 %) | 169 ms | 1.084 |
Gefilterter Index | 367 MB | 4 MB (+1 %) | 170 ms | 1.084 |
Wie Sie also sehen können, liefert der gefilterte Index Leistungsverbesserungen, die fast identisch mit denen des nicht gefilterten Index sind (da beide in der Lage sind, ihre Daten mit der gleichen Anzahl von Lesevorgängen zu erhalten), jedoch bei einem viel geringeren Speicherplatz Kosten, da der gefilterte Index nur die Zeilen speichern und verwalten muss, die mit dem Filterprädikat übereinstimmen.
Lassen Sie uns nun die Tabelle wieder in ihren ursprünglichen Zustand versetzen:
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) und Michelle Ufford (@sqlfool) haben fantastische Arbeit geleistet, indem sie die Leistungsvorteile gefilterter Indizes auf ihre eigene Weise beschrieben haben, und Sie sollten sich auch ihre Posts ansehen:
- Michelle Ufford:Gefilterte Indizes:Was Sie wissen müssen
- Tim Chapman:Die Freuden gefilterter Indizes
Außerdem ANSI-konforme Unique Constraints (sozusagen)
Ich dachte, ich würde auch kurz ANSI-konforme Unique Constraints erwähnen. In SQL Server 2005 würden wir eine Unique-Einschränkung wie diese erstellen:
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Wir könnten auch einen eindeutigen, nicht geclusterten Index anstelle einer Einschränkung erstellen; die zugrunde liegende Implementierung ist im Wesentlichen dieselbe.)
Nun, das ist kein Problem, wenn SSNs zum Zeitpunkt der Eingabe bekannt sind:
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
Es ist auch in Ordnung, wenn wir gelegentlich eine Sozialversicherungsnummer haben, die zum Zeitpunkt der Einreise nicht bekannt ist (denken Sie an einen Visumantragsteller oder vielleicht sogar einen ausländischen Arbeitnehmer, der keine Sozialversicherungsnummer hat und niemals haben wird):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
So weit, ist es gut. Aber was passiert, wenn wir eine Sekunde haben Mitarbeiter mit unbekannter Sozialversicherungsnummer?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Ergebnis:
Nachricht 2627, Level 14, Status 1, Zeile 1Verletzung der UNIQUE KEY-Einschränkung 'UQ_SSN'. Doppelter Schlüssel kann nicht in Objekt „dbo.Personnel“ eingefügt werden. Der doppelte Schlüsselwert ist (
Die Anweisung wurde beendet.
In dieser Spalte kann also immer nur ein NULL-Wert vorhanden sein. Im Gegensatz zu den meisten Szenarien handelt es sich hier um einen Fall, in dem SQL Server zwei NULL-Werte als gleich behandelt (anstatt festzustellen, dass die Gleichheit einfach unbekannt und damit falsch ist). Die Leute beschweren sich seit Jahren über diese Inkonsistenz.
Wenn dies erforderlich ist, können wir dies jetzt mit gefilterten Indizes umgehen:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Jetzt funktioniert unsere vierte Einfügung einwandfrei, da die Eindeutigkeit nur für die Nicht-NULL-Werte erzwungen wird. Das ist eine Art Betrug, erfüllt aber die grundlegenden Anforderungen, die der ANSI-Standard beabsichtigt (obwohl SQL Server uns nicht erlaubt, ALTER TABLE ... ADD CONSTRAINT
zu verwenden Syntax zum Erstellen einer gefilterten eindeutigen Einschränkung).
Aber halt das Telefon
Dies sind großartige Beispiele dafür, was wir mit gefilterten Indizes tun können, aber es gibt viele Dinge, die wir immer noch nicht tun können, und einige Einschränkungen und Probleme, die daraus resultieren.
Statistikaktualisierungen
Dies ist meiner Meinung nach eine der wichtigeren Einschränkungen. Gefilterte Indizes profitieren nicht von der automatischen Aktualisierung von Statistiken basierend auf einer prozentualen Änderung der Teilmenge der Tabelle, die durch das Filterprädikat identifiziert wird; Es basiert (wie alle nicht gefilterten Indizes) auf Abwanderung für die gesamte Tabelle. Dies bedeutet, dass sich die Anzahl der Zeilen im Index je nach Prozentsatz der Tabelle im gefilterten Index vervierfachen oder halbieren kann und die Statistiken nur aktualisiert werden, wenn Sie dies manuell tun. Kimberly Tripp hat einige großartige Informationen dazu gegeben (und Gail Shaw nennt ein Beispiel, bei dem es 257.000 Aktualisierungen dauerte, bevor die Statistiken für einen gefilterten Index aktualisiert wurden, der nur 10.000 Zeilen enthielt):
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/
Außerdem hat Kimberlys Kollege Joe Sack (@JosephSack) einen Connect-Eintrag eingereicht, der vorschlägt, dieses Verhalten sowohl für gefilterte Indizes als auch für gefilterte Statistiken zu korrigieren.
Beschränkungen für Filterausdrücke
Es gibt mehrere Konstrukte, die Sie nicht in einem Filterprädikat verwenden können, z. B. NOT IN
, OR
und dynamische / nicht deterministische Prädikate wie WHERE col >= DATEADD(DAY, -1, GETDATE())
. Außerdem erkennt der Optimierer möglicherweise einen gefilterten Index nicht, wenn das Prädikat nicht exakt mit WHERE
übereinstimmt -Klausel in der Indexdefinition. Hier sind einige Connect-Elemente, die versuchen, hier Unterstützung für eine bessere Abdeckung zu gewinnen:
Gefilterter Index erlaubt keine Filter auf Disjunktionen | (geschlossen:beabsichtigt) |
Gefilterte Indexerstellung fehlgeschlagen mit NOT IN-Klausel | (geschlossen:beabsichtigt) |
Unterstützung für komplexere WHERE-Klauseln in gefilterten Indizes | (aktiv) |
Andere Nutzungsmöglichkeiten derzeit nicht möglich
Wir können derzeit keinen gefilterten Index für eine persistente berechnete Spalte erstellen, selbst wenn sie deterministisch ist. Wir können einen Fremdschlüssel nicht auf einen eindeutigen gefilterten Index verweisen; Wenn wir möchten, dass ein Index den Fremdschlüssel zusätzlich zu den vom gefilterten Index unterstützten Abfragen unterstützt, müssen wir einen zweiten, redundanten, nicht gefilterten Index erstellen. Und hier sind ein paar andere ähnliche Einschränkungen, die entweder übersehen oder noch nicht berücksichtigt wurden:
Es sollte möglich sein, einen gefilterten Index für eine deterministische persistent berechnete Spalte zu erstellen | (aktiv) |
Gefilterten eindeutigen Index als Kandidatenschlüssel für einen Fremdschlüssel zulassen | (aktiv) |
Fähigkeit, Filterindizes für indizierte Ansichten zu erstellen | (geschlossen:wird nicht behoben) |
Partitionierungsfehler 1908 – Partitionierung verbessern | (geschlossen:wird nicht behoben) |
"Gefilterten" COLUMNSTORE-INDEX ERSTELLEN | (aktiv) |
Probleme mit MERGE
Und MERGE
taucht mal wieder auf meiner "aufgepasst"-Liste auf:
MERGE wertet den gefilterten Index pro Zeile aus, nicht nach der Operation, was zu einer Verletzung des gefilterten Index führt | (geschlossen:wird nicht behoben) |
MERGE kann mit gefiltertem Index nicht aktualisiert werden | (geschlossen:behoben) |
MERGE-Anweisungsfehler, wenn INSERT/DELETE einen gefilterten Index verwendete | (aktiv) |
MERGE meldet fälschlicherweise Verstöße gegen eindeutige Schlüssel | (aktiv) |
Während einer dieser (scheinbar eng verwandten) Fehler besagt, dass er in SQL Server 2012 behoben wurde, müssen Sie sich möglicherweise an PSS wenden, wenn Sie auf eine Variation dieses Problems stoßen, insbesondere bei früheren Versionen (oder beenden Sie die Verwendung von MERGE , wie ich bereits vorgeschlagen habe).
Tool / DMV / eingebaute Einschränkungen
Es gibt viele DMVs, DBCC-Befehle, Systemprozeduren und Client-Tools, auf die wir uns im Laufe der Zeit verlassen. Allerdings werden nicht alle diese Dinge aktualisiert, um neue Funktionen zu nutzen; gefilterte Indizes sind da keine Ausnahme. Die folgenden Connect-Elemente weisen auf einige Probleme hin, die Sie stolpern lassen können, wenn Sie erwarten, dass sie mit gefilterten Indizes funktionieren:
Es gibt keine Möglichkeit, einen gefilterten Index aus SSMS zu erstellen, während eine neue Tabelle entworfen wird | (geschlossen:wird nicht behoben) |
Der Filterausdruck eines gefilterten Index geht verloren, wenn eine Tabelle vom Tabellen-Designer geändert wird | (geschlossen:wird nicht behoben) |
Der Tabellen-Designer erstellt keine WHERE-Klausel in gefilterten Indizes | (aktiv) |
Der SSMS-Tabellen-Designer behält den Indexfilterausdruck beim Neuaufbau der Tabelle nicht bei | (geschlossen:wird nicht behoben) |
DBCC PAGE falsche Ausgabe mit gefilterten Indizes | (aktiv) |
SQL 2008 gefilterte Indexvorschläge aus DM-Ansichten und DTA | (geschlossen:wird nicht behoben) |
Verbesserungen der fehlenden Index-DMVs für gefilterte Indexe | (geschlossen:wird nicht behoben) |
Syntaxfehler beim Replizieren komprimierter gefilterter Indizes | (geschlossen:wird nicht behoben) |
Agent:Jobs verwenden nicht standardmäßige Optionen, wenn sie ein T-SQL-Skript ausführen | (geschlossen:wird nicht behoben) |
Das Anzeigen von Abhängigkeiten schlägt mit Transact-SQL-Fehler 515 fehl | (aktiv) |
Das Anzeigen von Abhängigkeiten schlägt bei bestimmten Objekten fehl | (geschlossen:wird nicht behoben) |
Unterschiede der Indexoptionen werden beim Schemavergleich für zwei Datenbanken nicht erkannt | (geschlossen:extern) |
Schlagen Sie vor, die Indexfilterbedingung in allen Ansichten von Indexinformationen anzuzeigen | (geschlossen:wird nicht behoben) |
sp_helpIndex-Ergebnisse sollten den Filterausdruck von Filterindizes enthalten | (aktiv) |
Sp_help, sp_columns, sp_helpindex für 2008-Features überladen | (geschlossen:wird nicht behoben) |
Halten Sie bei den letzten dreien nicht den Atem an – Microsoft wird wahrscheinlich keine Zeit in die sp_-Prozeduren, DMVs, INFORMATION_SCHEMA-Ansichten usw. investieren. Sehen Sie sich stattdessen Kimberly Tripps sp_helpindex-Umschreibungen an, die Informationen über gefilterte Indizes enthalten mit anderen neuen Funktionen, die Microsoft zurückgelassen hat.
Einschränkungen des Optimierungstools
Es gibt mehrere Connect-Elemente, die Fälle beschreiben, in denen gefilterte Indizes vom Optimierer *verwendet* werden könnten, aber stattdessen ignoriert werden. In manchen Fällen werden diese nicht als „Bugs“, sondern eher als „Funktionslücken“ betrachtet…
SQL verwendet keinen gefilterten Index für eine einfache Abfrage | (geschlossen:beabsichtigt) |
Ausführungsplan für gefilterten Index ist nicht optimiert | (geschlossen:wird nicht behoben) |
Gefilterter Index nicht verwendet und Schlüsselsuche ohne Ausgabe | (geschlossen:wird nicht behoben) |
Die Verwendung des gefilterten Index in der BIT-Spalte hängt vom genauen SQL-Ausdruck ab, der in der WHERE-Klausel verwendet wird | (aktiv) |
Linked-Server-Abfrage wird nicht richtig optimiert, wenn ein gefilterter eindeutiger Index vorhanden ist | (geschlossen:wird nicht behoben) |
Row_Number() liefert unvorhersehbare Ergebnisse über Verbindungsserver, wo gefilterte Indizes verwendet werden | (geschlossen:keine Repro) |
Offensichtlicher gefilterter Index wird von QP nicht verwendet | (geschlossen:beabsichtigt) |
Eindeutige gefilterte Indizes als eindeutig erkennen | (aktiv) |
Paul White (@SQL_Kiwi) hat kürzlich hier auf SQLPerformance.com einen Post gepostet, der ausführlich auf einige der oben genannten Einschränkungen des Optimierungsprogramms eingeht.
Und Tim Chapman hat einen großartigen Beitrag geschrieben, in dem er einige andere Einschränkungen von gefilterten Indizes umreißt – wie die Unfähigkeit, das Prädikat einer lokalen Variablen zuzuordnen (behoben in 2008 R2 SP1) und die Unfähigkeit, einen gefilterten Index in einem Indexhinweis anzugeben.
Schlussfolgerung
Gefilterte Indizes haben großes Potenzial, und ich hatte sehr große Hoffnungen in sie gesetzt, als sie erstmals in SQL Server 2008 eingeführt wurden. Die meisten Einschränkungen, die mit ihrer ersten Version geliefert wurden, bestehen jedoch noch heute, eineinhalb (oder zwei, je nach Ihrem Perspektive) Hauptversionen später. Das Obige scheint eine ziemlich umfangreiche Wäscheliste von Punkten zu sein, die angegangen werden müssen, aber ich wollte nicht, dass es so rüberkommt. Ich möchte nur, dass die Leute sich der enormen Anzahl potenzieller Probleme bewusst sind, die sie berücksichtigen müssen, wenn sie gefilterte Indizes nutzen.