Database
 sql >> Datenbank >  >> RDS >> Database

Gefilterte Indizes und INCLUDEd-Spalten

Gefilterte Indizes sind erstaunlich leistungsfähig, aber ich sehe immer noch einige Verwirrung darüber – insbesondere über die Spalten, die in den Filtern verwendet werden, und was passiert, wenn Sie die Filter verschärfen möchten.

In einer kürzlich auf dba.stackexchange gestellten Frage wurde um Hilfe gebeten, warum im Filter eines gefilterten Index verwendete Spalten in die „eingeschlossenen“ Spalten des Index aufgenommen werden sollten. Ausgezeichnete Frage – außer dass ich das Gefühl hatte, dass es mit einer schlechten Prämisse begann, weil diese Spalten nicht in den Index aufgenommen werden sollten . Ja, sie helfen, aber nicht so, wie die Frage vermuten lässt.

Um Ihnen zu ersparen, sich die Frage selbst anzusehen, hier eine kurze Zusammenfassung:

Um diese Abfrage zu beantworten…

SELECT Id, DisplayName 
FROM Users 
WHERE Reputation > 400000;

…der folgende gefilterte Index ist ziemlich gut:

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

Aber obwohl dieser Index vorhanden ist, empfiehlt der Abfrageoptimierer den folgenden Index, wenn der gefilterte Wert beispielsweise auf 450000 festgelegt wird.

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

Ich umschreibe die Frage hier ein wenig, was damit beginnt, sich auf diese Situation zu beziehen, und dann ein anderes Beispiel baut, aber die Idee ist die gleiche. Ich wollte die Dinge nur nicht noch komplizierter machen, indem ich eine separate Tabelle einbeziehe.

Der Punkt ist – der vom QO vorgeschlagene Index ist der ursprüngliche Index, aber auf den Kopf gestellt. Der ursprüngliche Index hatte Reputation in der INCLUDE-Liste und DisplayName und ID als Schlüsselspalten, während der neue empfohlene Index umgekehrt ist, mit Reputation als Schlüsselspalte und DisplayName &ID in INCLUDE. Sehen wir uns an, warum.

Die Frage bezieht sich auf einen Beitrag von Erik Darling, in dem er erklärt, dass er die obige Abfrage „450.000“ optimiert hat, indem er Reputation in die Spalte INCLUDE eingefügt hat. Erik zeigt, dass ohne Reputation in der INCLUDE-Liste eine Abfrage, die nach einem höheren Reputationswert filtert, Lookups durchführen muss (schlecht!) oder vielleicht sogar den gefilterten Index ganz aufgeben muss (möglicherweise sogar noch schlimmer). Er kommt zu dem Schluss, dass die Reputation-Spalte in der INCLUDE-Liste SQL über Statistiken verfügt, so dass es bessere Entscheidungen treffen kann, und zeigt, dass mit Reputation in INCLUDE eine Vielzahl von Abfragen, die alle nach höheren Reputationswerten filtern, seinen gefilterten Index scannen.

In einer Antwort auf die Frage zu dba.stackexchange weist Brent Ozar darauf hin, dass Eriks Verbesserungen nicht besonders groß sind, da sie Scans verursachen. Ich werde darauf zurückkommen, weil es ein interessanter Punkt an sich ist und etwas falsch.

Lassen Sie uns zunächst ein wenig über Indizes im Allgemeinen nachdenken.

Ein Index gibt einem Datensatz eine geordnete Struktur. (Ich könnte pedantisch sein und darauf hinweisen, dass Sie beim Durchlesen der Daten in einem Index von Anfang bis Ende auf scheinbar willkürliche Weise von Seite zu Seite springen könnten, aber dennoch, während Sie die Seiten durchlesen, folgen Sie den Hinweisen von einer Seite zur anderen im nächsten können Sie sicher sein, dass die Daten geordnet sind. Innerhalb jeder Seite können Sie sogar herumspringen, um die Daten der Reihe nach zu lesen, aber es gibt eine Liste, die Ihnen zeigt, welche Teile (Slots) der Seite in welcher Reihenfolge gelesen werden sollten Meine Pedanterie hat keinen Sinn, außer denen zu antworten, die ebenso pedantisch kommentieren, wenn ich es nicht tue.)

Und diese Reihenfolge ist nach den Schlüsselspalten – das ist der einfache Teil, den jeder bekommt. Dies ist nicht nur nützlich, um ein späteres Neuordnen der Daten zu vermeiden, sondern auch, um bestimmte Zeilen oder Zeilenbereiche anhand dieser Spalten schnell zu finden.

Die Blattebenen des Index enthalten die Werte in beliebigen Spalten in der INCLUDE-Liste oder im Falle eines Clustered-Index die Werte aller Spalten in der Tabelle (mit Ausnahme von nicht persistenten berechneten Spalten). Die anderen Ebenen im Index enthalten nur die Schlüsselspalten und (wenn der Index nicht eindeutig ist) die eindeutige Adresse der Zeile – entweder die Schlüssel des Clustered-Index (mit dem Uniquifier der Zeile, wenn der Clustered-Index auch nicht eindeutig ist). ) oder den RowID-Wert für einen Heap, genug, um einen einfachen Zugriff auf alle anderen Spaltenwerte für die Zeile zu ermöglichen. Die Blattebenen enthalten auch alle „Adress“-Informationen.

Aber das ist nicht das Interessante an diesem Beitrag. Das Interessante an diesem Beitrag ist, was ich mit "auf einen Datensatz" meine. Erinnern Sie sich, dass ich gesagt habe:„Ein Index bietet einem Datensatz eine geordnete Struktur ".

In einem gruppierten Index ist dieser Datensatz die gesamte Tabelle, aber es könnte auch etwas anderes sein. Sie können sich wahrscheinlich bereits vorstellen, dass die meisten Non-Cluster-Indizes nicht alle Spalten der Tabelle umfassen. Dies ist eines der Dinge, die Non-Cluster-Indizes so nützlich machen, weil sie normalerweise viel kleiner sind als die zugrunde liegende Tabelle.

Im Fall einer indizierten Ansicht könnte unser Datensatz die Ergebnisse einer ganzen Abfrage sein, einschließlich Verknüpfungen über viele Tabellen hinweg! Das ist für einen anderen Beitrag.

Aber in einem gefilterten Index ist es nicht nur eine Kopie einer Teilmenge von Spalten, sondern auch eine Teilmenge von Zeilen. Im Beispiel hier umfasst der Index also nur die Benutzer mit mehr als 400.000 Reputation.

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

Dieser Index nimmt die Benutzer, die mehr als 400.000 Reputation haben, und ordnet sie nach Anzeigename und ID. Es kann eindeutig sein, da (angenommen) die ID-Spalte bereits eindeutig ist. Wenn Sie etwas Ähnliches auf Ihrem eigenen Tisch ausprobieren, müssen Sie möglicherweise vorsichtig sein.

Aber an diesem Punkt kümmert sich der Index nicht darum, wie die Reputation für jeden Benutzer ist – es kümmert sich nur darum, ob die Reputation hoch genug ist, um im Index zu sein oder nicht. Wenn die Reputation eines Benutzers aktualisiert wird und den Schwellenwert überschreitet, werden der Anzeigename und die ID dieses Benutzers in den Index eingefügt. Wenn er darunter fällt, wird er aus dem Index gelöscht. Es ist so, als hätten wir einen separaten Tisch für die High Roller, außer dass wir Leute in diesen Tisch bekommen, indem wir ihren Reputationswert über die 400.000-Schwelle in der zugrunde liegenden Tabelle erhöhen. Dies ist möglich, ohne den Reputationswert selbst speichern zu müssen.

Wenn wir also jetzt Leute finden wollen, die einen Schwellenwert von über 450.000 haben, fehlen diesem Index einige Informationen.

Sicher, wir könnten getrost sagen, dass jeder, den wir finden, in diesem Index enthalten ist – aber der Index selbst enthält nicht genügend Informationen, um weiter nach Reputation zu filtern. Wenn ich Ihnen sagen würde, ich hätte eine alphabetische Liste der Oscar-prämierten Filme für den besten Film aus den 1990er Jahren (American Beauty, Braveheart, Der mit dem Wolf tanzt, English Patient, Forrest Gump, Schindlers Liste, Shakespeare in Love, Silence of the Lambs, Titanic, Unforgiven) , dann kann ich Ihnen versichern, dass die Gewinner für 1994-1996 eine Teilmenge davon sein würden, aber ich kann die Frage nicht beantworten, ohne vorher weitere Informationen zu erhalten.

Offensichtlich wäre mein gefilterter Index nützlicher, wenn ich das Jahr eingeschlossen hätte, und möglicherweise sogar noch nützlicher, wenn das Jahr eine Schlüsselspalte wäre, da meine neue Abfrage diejenigen für 1994-1996 finden möchte. Aber ich habe diesen Index wahrscheinlich um eine Abfrage herum entworfen, um alle Filme aus den 1990er Jahren in alphabetischer Reihenfolge aufzulisten. Diese Abfrage kümmert sich nicht um das tatsächliche Jahr, sondern nur darum, ob es in den 1990er Jahren liegt oder nicht, und ich muss nicht einmal das Jahr zurückgeben – nur den Titel – damit ich meinen gefilterten Index scannen kann, um die Ergebnisse zu erhalten. Für diese Abfrage muss ich nicht einmal die Ergebnisse neu ordnen oder den Ausgangspunkt finden – mein Index ist wirklich perfekt.

Ein praktischeres Beispiel dafür, sich nicht um den Wert der Spalte im Filter zu kümmern, ist der Status, z. B.:

WHERE IsActive = 1

Ich sehe häufig Code, der Daten von einer Tabelle in eine andere verschiebt, wenn Zeilen nicht mehr "aktiv" sind. Die Leute wollen nicht, dass alte Zeilen ihre Tabelle überladen, und sie erkennen, dass ihre „heißen“ Daten nur eine kleine Teilmenge aller ihrer Daten sind. Also verschieben sie ihre Kühldaten in eine Archivtabelle und halten ihre Active-Tabelle klein.

Ein gefilterter Index kann dies für Sie erledigen. Hinter den Kulissen. Sobald Sie die Zeile aktualisieren und diese IsActive-Spalte auf etwas anderes als 1 ändern. Wenn Sie nur daran interessiert sind, aktive Daten in den meisten Ihrer Indizes zu haben, dann sind gefilterte Indizes ideal. Es bringt sogar Zeilen zurück in die Indizes, wenn sich der IsActive-Wert wieder auf 1 ändert.

Aber Sie müssen IsActive nicht in die INCLUDE-Liste aufnehmen, um dies zu erreichen. Warum sollten Sie den Wert speichern wollen – Sie wissen bereits, was der Wert ist – es ist 1! Wenn Sie nicht darum bitten, den Wert zurückzugeben, sollten Sie ihn nicht benötigen. Und warum sollten Sie den Wert zurückgeben, wenn Sie bereits wissen, dass die Antwort 1 ist, oder?! Abgesehen davon werden frustrierenderweise die Statistiken, auf die sich Erik in seinem Beitrag bezieht, davon profitieren, dass sie in der INCLUDE-Liste stehen. Sie brauchen es nicht für die Abfrage, aber Sie sollten es für die Statistik verwenden.

Lassen Sie uns darüber nachdenken, was der Abfrageoptimierer tun muss, um die Nützlichkeit eines Indexes herauszufinden.

Bevor es überhaupt viel tun kann, muss es überlegen, ob der Index ein Kandidat ist. Es hat keinen Sinn, einen Index zu verwenden, wenn er nicht alle Zeilen enthält, die möglicherweise benötigt werden – es sei denn, wir haben eine effektive Möglichkeit, den Rest zu erhalten. Wenn ich Filme von 1985-1995 möchte, dann ist mein Index der 1990er-Filme ziemlich sinnlos. Aber für 1994-1996 ist es vielleicht nicht schlecht.

An diesem Punkt muss ich, genau wie bei jeder Indexüberlegung, darüber nachdenken, ob es ausreicht, um die Daten zu finden und in eine Reihenfolge zu bringen, die hilft, den Rest der Abfrage auszuführen (möglicherweise für einen Merge Join, Stream Aggregate, befriedigend). ein ORDER BY oder verschiedene andere Gründe). Wenn mein Abfragefilter genau mit dem Indexfilter übereinstimmt, muss ich nicht weiter filtern – es reicht, nur den Index zu verwenden. Das klingt großartig, aber wenn es nicht genau übereinstimmt, wenn mein Abfragefilter strenger ist als der Indexfilter (wie mein Beispiel 1994-1996 oder Eriks 450.000), brauche ich diese Jahreswerte oder Reputationswerte zu überprüfen – hoffentlich bekomme ich sie entweder von INCLUDEd auf Blattebene oder irgendwo in meinen Schlüsselspalten. Wenn sie nicht im Index sind, muss ich für jede Zeile in meinem gefilterten Index eine Suche durchführen (und idealerweise eine Vorstellung davon haben, wie oft meine Suche aufgerufen wird, was die Statistiken sind, die Erik will die Spalte enthalten für).

Im Idealfall ist jeder Index, den ich verwenden möchte, korrekt geordnet (über die Schlüssel), enthält alle Spalten, die ich zurückgeben muss, und ist auf genau die Zeilen vorgefiltert, die ich benötige. Das wäre der perfekte Index, und mein Ausführungsplan wäre ein Scan.

Das ist richtig, ein SCAN. Kein Seek, sondern ein Scan. Es beginnt auf der ersten Seite meines Index und gibt mir Zeilen, bis ich so viele wie nötig habe oder bis keine Zeilen mehr zurückzugeben sind. Keine überspringen, nicht sortieren – mir nur die Reihen in der richtigen Reihenfolge geben.

Ein Seek würde darauf hindeuten, dass ich nicht den gesamten Index benötige, was bedeutet, dass ich Ressourcen verschwende, um diesen Teil des Index zu verwalten, und um ihn abzufragen, muss ich den Ausgangspunkt finden und die Zeilen ständig überprüfen, um zu sehen, ob ich es getan habe das Ende treffen oder nicht. Wenn mein Scan ein Prädikat hat, muss ich natürlich mehr Daten durchsuchen (und testen), als ich brauche, aber wenn meine Indexfilter perfekt sind, sollte der Abfrageoptimierer dies erkennen und diese Prüfungen nicht durchführen müssen .

Abschließende Gedanken

INCLUDEs sind für gefilterte Indizes nicht kritisch. Sie sind nützlich, um einen einfachen Zugriff auf Spalten zu ermöglichen, die für Ihre Abfrage nützlich sein könnten, und wenn Sie den Inhalt Ihres gefilterten Index durch eine beliebige Spalte einschränken, unabhängig davon, ob sie im Filter erwähnt wird oder nicht, sollten Sie erwägen, diese Spalte zu verwenden die Mischung. Aber an diesem Punkt sollten Sie sich fragen, ob der Filter Ihres Index der richtige ist, was Sie sonst noch in Ihrer INCLUDE-Liste haben sollten und sogar, was die Schlüsselspalte(n) sein sollten. Eriks Abfragen funktionierten nicht gut, weil er Informationen benötigte, die nicht im Index enthalten waren, obwohl er die Spalte im Filter erwähnt hatte. Er fand auch eine gute Verwendung für die Statistiken, und ich würde Sie trotzdem ermutigen, die Filterspalten aus diesem Grund einzubeziehen. Aber wenn Sie sie in ein INCLUDE stecken, können sie nicht plötzlich mit einem Seek beginnen, denn so funktioniert kein Index, ob gefiltert oder nicht.

Ich möchte, dass Sie, lieber Leser, gefilterte Indizes wirklich gut verstehen. Sie sind unglaublich nützlich und können Teil Ihres gesamten Datenbankdesigns werden, wenn Sie sie sich wie eigenständige Tabellen vorstellen. Sie sind auch ein Grund dafür, immer die Einstellungen ANSI_NULLs und QUOTED_IDENTIFIER zu verwenden, da Sie Fehler vom gefilterten Index erhalten, es sei denn, diese Einstellungen sind aktiviert, aber Sie stellen hoffentlich bereits sicher, dass sie sowieso immer aktiviert sind.

Oh, und diese Filme waren Forrest Gump, Braveheart und The English Patient.

@rob_farley