Clustered Indexes

By Frank Kalis

Posted on Sep 1, 2005 von in SQL Server

Wenn man einen Clustered Index erstellt, sollte dieser nach Möglichkeit als UNIQUE erstellt werden. Man muß UNIQUE explizit vorgeben, da SQL Server dies nicht standardmäßig annimmt. Der Grund dafür liegt in den Interna des Servers. Unter der Haube ist jeder Clustered Index UNIQUE. Wurde der Index nicht so erstellt, greift SQL Server automatisch ein, wenn - und auch nur dann - Duplikate in den oder die Index Schlüssel eingefügt werden sollen und fügt einen Integer Wert hinzu, der die Einmaligkeit der Schlüssel garantiert. Dieser 4 Byte Integer Wert wird auch "Uniquifier" genannt. Er verbreitert den Index entsprechend und führt dazu, daß mehr IO Operationen notwendig sind, um die relevanten Daten abzurufen. Die Performance ist negativ betroffen.

Es existiert allerdings noch ein weiterer guter Grund, warum idealerweise der Clustered Index UNIQUE sein sollte. Dazu muß man sich vor Augen führen, daß die Schlüssel des Clustered Index in der Blattebene der Nonclustered Indexes gespeichert werden. Erstellt man nun den Clustered Index neu (oder reorganisiert ihn), müssen ALLE Nonclustered Indexes der Tabelle ebenfalls neu erstellt werden, wenn der Clustered Index nicht UNIQUE erstellt wurde. Während diese Vorgehensweise die übliche war vor SQL Server 2000 Service Pack 2, hat sich das Verfahren mit der Einführung von SP 2 geändert. Jetzt werden die Nonclustered Indexes nur unter der Voraussetzung neu erstellt, daß der Clustered Index nicht als UNIQUE kreiiert wurde. Der Grund dafür liegt darin, daß während der Neuerstellung oder Reorganisation des Clustered Index dieser "Uniquifier" neu generiert wird. Bei einem nicht UNIQUE Clustered Index muß sich zwangsläufig diese Veränderung auch in den Nonclustered Indexes widerspiegeln, d.h. sie müssen ebenfalls neu erstellt werden. Wurde hingegen der Index als UNIQUE erstellt, gibt es keinen Uniquifier und in der Blattebene der Nonclustered Indexes finden sich nur die Schlüssel des Clustered Index. Da diese sich bei Neuerstellung nicht ändern, braucht auch nicht an den Nonclustered Indexes geändert zu werden.

*****

Nach Möglichkeit sollte es vermieden werden, einen Clustered Index auf einer GUID Spalte (Uniqueidentifier Datentyp) zu erstellen. Hier sind einige Gründe:

  1. GUID belegen 16 Bytes Speicherplatz. Dies ist, zum Vergleich, 4x so viel wie eine INTEGER Spalte. GUIDs machen den Index deutlich breiter, was dazu führt, daß weniger Daten auf einer Datenseite gespeichert werden können. SQL Server muß deshalb mehr IO Operationen ausführen, um die relevanten Daten abzurufen. Dies dürfte sich negativ auf die Performance auswirken.
  2. Die Schlüssel eines Clustered Index werden zusätzlich zu den eigentlichen Index Schlüsseln in der Blattebene jedes Nonclustered Index einer Tabelle gespeichert. Dies führt ebenfalls zu einer Verbreiterung der Nonclustered Indexes, die Einfluß auf die Performance hat.
  3. GUIDs sind von Natur aus "zufällig". Das heißt, man kann nicht voraussagen, ob der "nächste" Wert vor oder hinter dem letzten eingefügt werden muß. So muß man entweder entsprechend viel Platz auf einer Datenseite lassen (und damit Speicherplatz wenig effektiv nutzen) oder sich ggfs. mit Page Splittings auseinandersetzen beim einem ausgelasteten System.
  4. GUIDs sind wenig intuitiv. Aus diesen Grund darf bezweifelt werden, daß die wichtigsten und kritischsten Abfragen an ein System sich an einer GUID Spalte ausrichten oder daß man häufig GUIDs sortiert oder gruppiert. Da ein geschickt ausgewählter Clustered Index eine deutlich verbesserte Performance bewirken kann, sollte man ihn sich für eine besser geeignete Kombination von Spalte(n) aufheben.
  5. Ist man durch externe Einflüße gezwungen, GUIDs zu verwenden (z. B. in Replikationen Szenarien), und man entscheidet sich, die GUID Spalte auch zum PRIMARY KEY der Tabelle zu machen, sollte man darauf achten, daß SQL Server diesen PRIMARY KEY nicht auch automatisch zum Clustered Index macht (sofern bisher noch kein Clustered Index vorhanden ist auf dieser Tabelle). Dies kann dadurch erreicht werden, indem man entweder
    1. Explizit das NONCLUSTERED Schlüsselwort bei der Erstellung des Index via Skript angibt.
    2. Den Haken aus der entsprechende Option entfernt, falls man den Index über das graphische Management Interface erstellt.
  6. Zu guter Letzt, und vielleicht auch weniger wichtig. GUID sind nicht ANSI komform und daher nicht unbedingt portabel.

*****

 Als Faustregel gilt: Jede Tabelle sollte einen Clustered Index besitzen. Im allgemeinen, jedoch nicht immer stur anwendend, sollte der Clustered Index auf einer Spalte mit stetig steigenden eindeutigen Werten liegen. In vielen Fällen ist der Primärschlüssel einer Tabelle auch ein guter Kandidat für den Clustered Index.

Falls man Erfahrung mit Performance Tuning im SQL Server 6.5 hat, hat man vielleicht gehört, daß es keine gute Idee ist, den Clustered Index auf eine Spalte zu legen, deren Werte stetig steigen, da dies "Hotspots" auf der Festplatte verursachen kann, die zu Performance Problemen führen können. Dies gilt für SQL Server 6.5.

In SQL Server 7.0 und 2000 sind "Hotspots" generell kein Problem. Man müßte über 1.000 Transaktionen pro Sekunde haben, bevor so ein "Hotspot" negativ die Performance beeinflussen könnte. Tatsächlich kann so ein "Hotspot" vorteilhaft unter diesen Umständen sein, da er Page Splits eliminieren kann.

Warum? Angenommen man fügt neue Zeilen zu einer Tabelle hinzu, deren Primärschlüssel auch der Clustered Index ist. Die Werte in dieser Spalte steigen monoton steigend an. Dies bedeutet, daß jedes INSERT physikalisch hinter dem vorherigen auf der Festplatte eingefügt wird. Dadurch können keine Page Split während der INSERTs vorkommen, was für sich genommen den Overhead hilft zu verringern. Grund dafür ist SQL Servers Fähigkeit festzustellen, ob die hinzuzufügenden Daten bei einer monoton steigenden Folge auf eine Seite passen oder nicht. Pasen sie nicht mehr auf die Seite, wird eine neue allokiert und Page Splits kommen erst gar nicht vor.

Fügt man eine Menge Daten in eine Heap Tabelle (also eine Tabelle ohne Clustered Index) ein, werden die Daten in keiner speziellen Reihenfolge eingefügt. Egal, ob sie monoton steigend sind oder nicht, die Daten werden dort eingefügt, wo SQL Server feststellt, daß genügend Platz vorhanden ist. Üblicherweise führt dies dazu, daß SQL Server aber auc mehr Arbeit verrichten muß, wenn die Daten von der Festplatte angefordert werden. Fügt man nun einen Clustered Index zu so einer Tabelle hinzu, werden die Daten sequentiell in die Datenseiten geschrieben und im allgemeinen benötigt man weniger I/O Disk Operationen, um die Daten von der Festplatte abzurufen.

Falls Daten in einem eher zufälligen Muster in einen Clustered Index eingefügt werden, werden die Daten oftmals eher zufällig physikalisch in die Datenseiten eingefügt, was vergleichbar ist mit dem Problem des Einfügens in eine Heap Tabelle.

Nochmals, die beste allgemeingültige Empfehlung besteht darin, den Clustered Index auf eine Spalte zu legen, deren Werte monoton steigend sind (falls es eine solche Spalte in der Tabelle gibt). Besonders gilt dies für eine Tabelle, in die häufig INSERTs, UPDATEs und DELETEs stattfinden. Sind hingegen die Daten in der Tabelle eher statisch und verändern sich kaum, werden aber häufig über SELECT Statements abgefragt, ist dieser Rat weniger hilfreich, und andere Optionen für den Clustered Index sollten in Betracht gezogen werden. Im weiteren Verlauf finden sich weitere Tips für solche Situationen, in denen man den Clustered Index auf andere Spalten legen sollte.

*****

Hier sind einige gute Gründe, warum jede Tabelle einen Clustered Index besitzen sollte.

Man sollte bedenken, daß ein Clustered Index die physikalische Sortierung der Daten einer Tabelle gemäß den Schlüsselwerten bestimmt. Eine Heap Tabelle unterliegt nicht solchen Mechanismen. Ihre Daten werden in keiner bestimmten physikalischen Sortierung gespeichert.

Immer wenn man die Spalte(n) abfragt, die für den Clustered Index verwendet werden, hat SQL Server die Möglichkeit, die Daten sequentiell aus dem Clustered Index in Extent Blöcken (= 8 Datenseiten oder 64 kb) auf einmal zu lesen. Dies macht es dem Disk Subsystem sehr einfach, die Daten sehr schnell zu lesen, besonders wenn viele Daten abgerufen werden.

Liegt aber hingegen eine Heap Tabelle vor, muß SQL Server die Daten eher zufällig von Platte lesen (auch wenn ein angemessener Nonclustered (noncovering) Index vorhanden ist). Dies bedeutet eine Menge Extraarbeit, die das Disksubsystem verrichten muß, um die gleichen Daten zurückzugeben. Dies geht zu Lasten der Performance.

Ein weiterer Nachteil einer Heap Tabelle zeigt sich, wenn man Indexes neu erstellt, um Fragmentierung zu reduzieren. Heaps können nicht defragmentiert werden, da sie keine Indizes sind. Dies bedeutet, daß über die Zeit, die Daten tendenziell eher mehr und mehr fragmentiert werden, was weiter die Performance verschlechtert. Durch das Hinzufügen eines Clustered Index kann man sicherstellen, daß eine Tabelle defragmentiert wird, wenn die Indizes neu erstellt werden.

*****

Da es nur einen Clustered Index pro Tabelle geben kann, sollte man sich besonders viel Zeit nehmen, um herauszufinden, wie dieser aussehen sollte. Dabei sollte man die Abfragen an diese Tabelle in Betracht ziehen und eine fundierte Meinung dazu haben, welche dieser Abfragen, die kritischste (und unter Umständen am häufigsten ausgeführte) ist und ob gerade diese Abfrage von dem gewählten Clustered Index profitieren würde.

*****

Clustered Indexes sind nützlich für Abfragen, die folgenden Kriterien erfüllen:

* Für Abfragen, die einen breiten Bereich abfragen oder wenn man ein sortiertes Ergebnis benötigt. Die Daten sind bereits durch den Index vorsortiert. Beispiele hierfür sind zum Beispiel BETWEEN, <, >, GROUP BY, ORDER BY und Aggregate wie MAX, MIN, and COUNT in Abfragen.

* Für Abfrage, in denen man nach einem einmaligen Wert (wie zum Beispiel eine Mitarbeiter-Nummer) sucht und alle oder fast alle Daten dieser zeile benötigt. Grund hierfür ist, daß die Abfrage durch den Index gecovered ist. Mit anderen Worten, sind die Daten, die man benötigt, der Index selber und SQL Server muß keine weiteren Daten lesen.

* Für Abfragen, die auf Spalten zugreifen, die über eine limitiert Anzahl unterschiedlicher Werte verfügen. so wie zum Beispiel Länder Daten. Hat aber die Spalte kaum unterschiedliche Daten, so wie Spalte mit "Ja" und "Nein" oder "Männlich" und "Weiblich", bedeutet es eine Verschwendung, dies Spalten für den Clustered Index zu verwenden.

* Für Abfragen, die in JOIN oder GROUP BY Klauseln verwendet werden.

* Für Abfragem, die eine Menge Daten zurückgeben sollen, nicht nur einige wenige. Dies liegt wieder daran, daß die Daten der Index selber sind und SQL Server nicht noch irgendwo anders nachschauen muß.

*****

Falls man vor einer Situation steht, in der man einen einzelnen, breiten Index (ein zusammengesetzter Index von 3 oder mehr Spalten) in einer Tabelle zu haben, während die restlichen Indizes dieser Tabelle (sofern vorhanden) nur über eine Spalte gehen, sollte man darüber nachdenken, den breiten Index zum Clustered Index zu machen, während die anderen Nonclustered erstellt werden sollten.

Warum? Ist der breite Index der Clustered Index bedeutet dies, daß die gesamte Tabelle der Index ist und kein großer zusätzlicher Plattenspeicher benötigt wird, um den Index zu erstellen. Ist hingegen der breite Index ein Nonclustered Index, muß SQL Server hierfür einen "relativ großen" neuen Index erstellen, der Speicherplatz in Anspruch nimmt.

*****

Man sollte vermeiden, den Clustered Index auf Spalten zu legen, die bereits durch Nonclustered Indizes abgedeckt werden. Dies führt zu redundanten Indizes. Man sollte geeignetere Spalten für den Clustered Index verwenden.

*****

Bei der Auswahl von potentiellen Spalten für den Clustered Index sollte man Spalten vermeiden, deren Daten häufig verändert werden. Jedes Mal wenn sich ein Wert eines Clustered Index verändert, müssen sämtliche anderen Nonclustered Indizes ebenfalls verändert werden, um diese Modifikation zu reflektieren, was einen nicht zu unterschätzenden Overhead darstellt.

*****

Bei der Auswahl der Spalte oder der Spalten für den Clustered Index, sollte man die Spalte verwenden, nach der am häufigsten gesucht wird. Bei einem Zusammengesetzten Clustered Index sollte diese Spalte am erster Stelle stehen.

*****

Falls eine Tabelle sowohl einen Clustered Index als auch Nonclustered Indizes enthält, wird die Performance optimiert, wenn der Clustered Index auf einer einzelnen Spalte liegt, die so kompakt als möglich ist. Grund dafür ist, daß sämtliche Nonclustered Indizes die Schlüssel des Clustered Index verwendet, um die Daten zu lokalisieren. Sowohl der Clustered Index als auch die anderen Indizes profitieren davon.

*****

Der Primärschlüssel einer Tabelle muß nicht zwingend auch deren Clustered Index sein. Dies ist zwar SQL Servers Standard, sofern man es nicht anders angibt, ist aber nicht immer die beste Wahl. Man sollte nur dann den Primärschlüssel auch zum Clustered Index machen, wenn man regelmäßig Range Abfragen über den Primärschlüssel ausführt oder wenn das Ergebnis anhand des Primärschlüssels sortiert sein soll.

*****

Löscht man einen Clustered Index, sollte man bedenken, daß man freien Speicherplatz braucht, der ca. 1.2 x so groß ist wie die Tabelle selbst.
Dies gilt auch, wenn ein Clustered Index neu erstellt werden soll.

*****

Tags: Tags:
Dieser Eintrag wurde eingetragen von und ist abgelegt unter SQL Server. Tags: ,

Noch kein Feedback


Formular wird geladen...