Leistungsunterschied zwischen gruppiertem und nicht gruppiertem Index

22

Ich habe gelesen Clusteredund Non Clustered Indexes.

Clustered Index- Es enthält Datenseiten. Das bedeutet, dass die vollständigen Zeileninformationen in der Clustered-Index-Spalte enthalten sind.

Non Clustered Index- Es enthält nur die Zeilenlokalisierungsinformationen in Form einer Spalte mit gruppiertem Index (falls verfügbar) oder den Dateiindikator + Seitenzahl + Gesamtanzahl der Zeilen auf einer Seite. Dies bedeutet, dass die Abfrage-Engine einen zusätzlichen Schritt ausführen muss, um die tatsächlichen Daten zu lokalisieren.

Abfrage - Wie kann ich den Unterschied in der Leistung mit Hilfe eines praktischen Beispiels überprüfen , da wir wissen , dass die Tabelle nur eine haben kann Clustered Indexund bietet sortingan dem Clustered Index Columnund Non Clustered Indexbietet nicht sortingund 999 unterstützen kann Non Clustered Indexesin SQL Server 2008in und 249 SQL Server 2005.


quelle
2
Leistungsunterschiede, wenn Sie was tun? Welche Art von Arbeit möchten Sie mit diesem Tisch erledigen? Es gibt keine einzige Lösung, die für jeden Bedarf
geeignet ist
2
Einige konkrete Diskussionen hier vielleicht. stackoverflow.com/questions/91688/… stackoverflow.com/questions/5070529/… stackoverflow.com/questions/1251636/… Wir könnten eine Dissertation über die Unterschiede zwischen geclusterten und nicht geclusterten Indizes schreiben, aber ich glaube nicht Ich würde alles sagen, was noch nicht da draußen ist, damit Sie es lesen können.
Aaron Bertrand
4
Sie haben geschrieben: "Dies bedeutet, dass die Abfrage-Engine einen zusätzlichen Schritt ausführen muss, um die tatsächlichen Daten zu finden." Eigentlich, wenn alles , was Sie Spalten benötigen , werden im Index abgedeckt , Sie nicht alle zusätzlichen Schritte ergreifen müssen , nachdem Sie Ihre Zielzeilen in dem nicht gruppierten Index. Nur wenn Sie Spalten benötigen, die nicht vom Nonclustered-Index abgedeckt werden, muss SQL Server eine Lesezeichensuche durchführen .
Nick Chammas

Antworten:

43

Sehr gute Frage, da es sich um ein so wichtiges Konzept handelt. Dies ist jedoch ein großes Thema, und ich zeige Ihnen, dass es sich um eine Vereinfachung handelt, damit Sie die grundlegenden Konzepte verstehen können.

Erstens, wenn Sie Clustered Index Think Table sehen . Wenn eine Tabelle in SQL Server keinen Clustered-Index enthält, handelt es sich um einen Heap. Durch das Erstellen eines Clustered-Index für die Tabelle wird die Tabelle tatsächlich in eine Struktur vom Typ B-Tree umgewandelt. Ihr Clustered-Index IST Ihre Tabelle, er ist nicht von der Tabelle getrennt

Haben Sie sich jemals gefragt, warum Sie nur einen Clustered-Index haben können? Wenn wir zwei Clustered-Indizes hätten, bräuchten wir zwei Kopien der Tabelle. Es enthält schließlich die Daten.

Ich werde versuchen, dies anhand eines einfachen Beispiels zu erklären.

HINWEIS: Ich habe die Tabelle in diesem Beispiel erstellt und sie mit über 3 Millionen zufälligen Einträgen gefüllt. Dann liefen die eigentlichen Abfragen und fügten die Ausführungspläne hier ein.

Was Sie wirklich verstehen müssen, ist die O-Notation oder die betriebliche Effizienz . Angenommen, Sie haben die folgende Tabelle.

CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [varchar](100) NOT NULL,
[CustomerSurname] [varchar](100) NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
[CustomerID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , IGNORE_DUP_KEY = OFF,ALLOW_ROW_LOCKS  = ON
  , ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Hier haben wir also eine Basistabelle mit einem gruppierten Schlüssel auf CustomerID (der Primärschlüssel wird standardmäßig gruppiert). Somit wird die Tabelle basierend auf dem Primärschlüssel CustomerID angeordnet / geordnet. Die Zwischenebenen enthalten die CustomerID-Werte. Die Datenseiten enthalten die gesamte Zeile, also die Tabellenzeile.

Wir erstellen auch einen nicht gruppierten Index für das Feld Kundenname. Der folgende Code wird es tun.

CREATE NONCLUSTERED INDEX [ix_Customer_CustomerName] ON [dbo].[Customer] 
 (
[CustomerName] ASC
 )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF
  , DROP_EXISTING = OFF, ONLINE = OFF
  , ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

In diesem Index finden Sie also auf den Datenseiten / Knoten auf Blattebene einen Zeiger auf die Zwischenebenen im Clustered-Index. Der Index ist um das Feld Kundenname angeordnet / geordnet. Somit enthält die Zwischenebene die CustomerName-Werte und die Blattebene enthält den Zeiger (diese Zeigerwerte sind tatsächlich die Primärschlüsselwerte oder die CustomerID-Spalte).

Richtig also, wenn wir folgende Abfrage ausführen:

SELECT * FROM Customer WHERE CustomerID = 1 

SQL liest den Clustered-Index wahrscheinlich über eine Suchoperation. Eine Suchoperation ist eine binäre Suche, die viel effizienter ist als eine Abtastung, die eine sequentielle Suche ist. In unserem obigen Beispiel wird der Index gelesen und durch die Verwendung einer binären Such-SQL können die Daten entfernt werden, die nicht den von uns gesuchten Kriterien entsprechen. Den Abfrageplan finden Sie im angehängten Screenshot.

Bildbeschreibung hier eingeben

Die Anzahl der Operationen oder die O-Notation für die Suchoperation ist also wie folgt:

  1. Führen Sie eine binäre Suche im Clustered-Index durch, indem Sie den gesuchten Wert mit den Werten auf der Zwischenebene vergleichen.
  2. Die übereinstimmenden Werte zurückgeben (denken Sie daran, dass der Clustered-Index alle Daten enthält und alle Spalten aus dem Index zurückgeben kann, da es sich um die Zeilendaten handelt.)

Es sind also zwei Operationen. Wenn wir jedoch die folgende Abfrage ausführen:

SELECT * FROM Customer WHERE CustomerName ='John'

SQL verwendet jetzt den nicht gruppierten Index für den Kundennamen, um die Suche durchzuführen. Da es sich jedoch um einen nicht gruppierten Index handelt, enthält dieser nicht alle Daten in der Zeile.

Daher durchsucht SQL die Zwischenebenen, um die passenden Datensätze zu finden, und durchsucht anschließend den geclusterten Index (auch als Tabelle bezeichnet) erneut nach den zurückgegebenen Werten, um die tatsächlichen Daten abzurufen. Das klingt verwirrend, ich weiß, aber lies weiter und alles wird klar.

Da unser nicht gruppierter Index nur das CustomerName-Feld (die in den Zwischenknoten gespeicherten indizierten Feldwerte) und den Zeiger auf die Daten enthält, bei denen es sich um die CustomerID handelt, enthält der Index keinen Datensatz des CustomerSurname. Der Kundenname muss aus dem gruppierten Index oder der gruppierten Tabelle abgerufen werden.

Beim Ausführen dieser Abfrage erhalte ich den folgenden Ausführungsplan:

Bildbeschreibung hier eingeben

In der obigen Abbildung sind zwei wichtige Punkte zu beachten

  1. SQL sagt, ich habe einen fehlenden Index (der Text ist grün). SQL schlägt vor, einen Index für CustomerName zu erstellen, der CustomerID und CustomerSurname enthält.
  2. Außerdem wird angezeigt, dass 99% der Zeit der Abfrage für die Schlüsselsuche im Primärschlüsselindex / Clustered-Index aufgewendet werden.

Warum schlägt SQL den Index für CustomerName erneut vor? Nun, da der Index nur die CustomerID und den CustomerName enthält, muss SQL noch den CustomerSurname aus der Tabelle / den Clustered-Indizes finden.

Wenn wir den Index erstellen und die Spalte CustomerSurname in den Index aufnehmen, kann SQL die gesamte Abfrage erfüllen, indem nur der nicht gruppierte Index gelesen wird. Aus diesem Grund schlägt SQL vor, meinen nicht gruppierten Index zu ändern.

Hier sehen Sie die zusätzliche Operation, die SQL ausführen muss, um die CustomerSurname-Spalte vom gruppierten Schlüssel abzurufen

Somit ist die Anzahl der Operationen wie folgt:

  1. Führen Sie eine binäre Suche für einen nicht gruppierten Index durch, indem Sie den gesuchten Wert mit den Werten auf der Zwischenebene vergleichen
  2. Lesen Sie für übereinstimmende Knoten den Knoten auf Blattebene, der den Zeiger für die Daten im Clustered-Index enthält (die Knoten auf Blattebene enthalten übrigens die Primärschlüsselwerte).
  3. Lesen Sie für jeden zurückgegebenen Wert den gruppierten Index (die Tabelle), um die Zeilenwerte hier herauszufinden. Wir würden dann den Kundennamen lesen.
  4. Gibt übereinstimmende Zeilen zurück

Das sind 4 Operationen, um die Werte herauszufinden. Doppelte Anzahl an Operationen im Vergleich zum Lesen des Clustered-Index. Das zeigt Ihnen, dass Ihr Clustered-Index Ihr leistungsstärkster Index ist, da er alle Daten enthält.

Also nur um einen letzten Punkt zu verdeutlichen. Warum sage ich, dass der Zeiger im nicht gruppierten Index der Primärschlüsselwert ist? Um zu demonstrieren, dass die Knoten auf Blattebene des nicht gruppierten Index den Primärschlüsselwert enthalten, ändere ich meine Abfrage in:

SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'

In dieser Abfrage kann SQL die CustomerID aus dem nicht gruppierten Index lesen. Der Clustered-Index muss nicht durchsucht werden. Dies können Sie dem Ausführungsplan entnehmen, der so aussieht.

Bildbeschreibung hier eingeben

Beachten Sie den Unterschied zwischen dieser Abfrage und der vorherigen Abfrage. Es gibt keine Suche. SQL kann alle Daten im nicht gruppierten Index finden

Hoffentlich können Sie anfangen zu verstehen, dass der Clustered-Index die Tabelle ist und Nicht-Clustered-Indizes NICHT alle Daten enthalten. Die Indizierung beschleunigt die Auswahl, da binäre Suchen durchgeführt werden können, aber nur Clustered-Indizes alle Daten enthalten. Daher führt eine Suche in einem nicht gruppierten Index fast immer dazu, dass Werte aus dem gruppierten Index geladen werden. Diese zusätzlichen Vorgänge führen dazu, dass nicht gruppierte Indizes weniger effizient sind als ein gruppierter Index.

Hoffe das klärt die Dinge auf. Wenn irgendetwas keinen Sinn ergibt, schreibe bitte einen Kommentar und ich versuche es zu klären. Es ist ziemlich spät hier und mein Gehirn fühlt sich ein bisschen platt. Zeit für einen roten Bullen.

Namphibianer
quelle
Ich habe eine Frage. WARUM sucht ein Index im nicht gruppierten Index für Kundenname nach dieser Abfrage? SELECT * FROM Kunde WHERE Kundenname = 'John'. Da es sich um einen nicht gruppierten Index handelt, wird der Benutzername nicht sortiert. Ein Index-Scan sollte also nicht durchgeführt werden.
ckv
Übrigens Tolle Antwort total verstanden bis auf die obige Frage.
ckv
1
Ein Index wird in der Reihenfolge der Daten sortiert. Beispielsweise würde es nach dem Kundennamen sortiert, da es sich um den indizierten Wert handelt. So ist es sortiert. Denken Sie daran, dass die Blattebene oder die Seiten noch gescannt werden müssen.
Namphibian
9

"Dies bedeutet, dass die Abfrage-Engine einen zusätzlichen Schritt ausführen muss, um die tatsächlichen Daten zu lokalisieren."

Nicht unbedingt - wenn der Index eine bestimmte Abfrage abdeckt, muss kein Trip zu den Datenseiten durchgeführt werden. Mit eingeschlossenen Spalten können auch zusätzliche Spalten zu einem nicht gruppierten Index hinzugefügt werden, damit dieser abgedeckt wird, ohne die Schlüsselgröße zu ändern.

Die endgültige Antwort lautet also: - Es kommt darauf an (dass Sie viel mehr Informationen benötigen, als Sie wirklich in einer einzigen Frage behandeln können) - Sie müssen alle Funktionen der Indizes verstehen, und der Ausführungsplan für eine bestimmte Abfrage kann von Ihren Erwartungen abweichen.

Eine allgemeine Faustregel ist, dass eine Tabelle immer einen Clustered-Index (und normalerweise eine Identität oder eine sequenzielle GUID) hat, aber nicht-Clustered-Indizes werden zur Verbesserung der Leistung hinzugefügt. Es gibt jedoch immer Ausnahmen - Heap-Tabellen haben einen Platz, breitere Clustered-Indizes haben einen Platz. Scheinbar redundante Indizes, die schmaler sind, um mehr Zeilen pro Seite aufzunehmen, haben einen Platz. usw. usw.

Und ich würde mir keine Sorgen um die Beschränkungen der verschiedenen zulässigen Indizes machen - das wird in vielen Beispielen aus der Praxis mit ziemlicher Sicherheit nicht zum Tragen kommen.

Cade Roux
quelle
2
+1 für there are always exceptions- zu viele Leute lassen dies aus und denken, dass jeder Clustered-Index ein int identityegal was sein sollte.
JNK