Bei der Wahl des Primärschlüssels wählt man normalerweise auch den Clustered Key. Die beiden werden oft verwechselt, aber man muss den Unterschied verstehen.
Primärschlüssel sind logische Geschäfte Elemente. Der Primärschlüssel wird von Ihrer Anwendung verwendet, um eine Entität zu identifizieren, und die Diskussion über Primärschlüssel dreht sich hauptsächlich darum, ob natürliche Schlüssel oder Ersatzschlüssel verwendet werden sollen. Die Links gehen viel detaillierter, aber die Grundidee ist, dass natürliche Schlüssel von einer vorhandenen Entitätseigenschaft wie ssn
abgeleitet werden oder phone number
, während Ersatzschlüssel in Bezug auf die Geschäftsentität keinerlei Bedeutung haben, wie id
oder rowid
und sie sind normalerweise vom Typ IDENTITY
oder eine Art uuid. Meine persönliche Meinung ist, dass Ersatzschlüssel natürlichen Schlüsseln überlegen sind, und die Wahl sollte immer Identitätswerte für nur lokale Anwendungen sein, Führer für jede Art von verteilten Daten. Ein Primärschlüssel ändert sich während der Lebensdauer der Entität nie.
Geclusterte Schlüssel sind der Schlüssel, der die physische Speicherung von Zeilen in der Tabelle definiert. Meistens überschneiden sie sich mit dem Primärschlüssel (der logischen Entitätskennung), aber das wird nicht wirklich erzwungen oder verlangt. Wenn die beiden unterschiedlich sind, bedeutet dies, dass es einen nicht gruppierten eindeutigen Index für die Tabelle gibt, der den Primärschlüssel implementiert. Geclusterte Schlüsselwerte können sich tatsächlich während der Lebensdauer der Zeile ändern, was dazu führt, dass die Zeile in der Tabelle physisch an eine neue Position verschoben wird. Wenn Sie den Primärschlüssel vom Cluster-Schlüssel trennen müssen (was manchmal der Fall ist), ist die Auswahl eines guten Cluster-Schlüssels erheblich schwieriger als die Auswahl eines Primärschlüssels. Es gibt zwei Hauptfaktoren, die Ihr Clustered-Key-Design bestimmen:
- Das vorherrschende Datenzugriffsmuster .
- Die Überlegungen zur Speicherung .
Datenzugriffsmuster . Darunter verstehe ich die Art und Weise, wie die Tabelle abgefragt und aktualisiert wird. Denken Sie daran, dass gruppierte Schlüssel die tatsächliche Reihenfolge der Zeilen in der Tabelle bestimmen. Für bestimmte Zugriffsmuster machen einige Layouts den entscheidenden Unterschied in Bezug auf die Abfragegeschwindigkeit oder die Aktualisierungsparallelität:
-
aktuelle vs. Archivdaten. In vielen Anwendungen wird häufig auf die Daten des aktuellen Monats zugegriffen, während selten auf die Daten der Vergangenheit zugegriffen wird. In solchen Fällen verwendet das Tabellendesign die Tabellenpartitionierung nach Transaktionsdatum, oft unter Verwendung eines Sliding-Window-Algorithmus. Die aktuelle Monatspartition wird in einer Dateigruppe gespeichert, die sich auf einer heißen, schnellen Festplatte befindet, die archivierten alten Daten werden in Dateigruppen verschoben, die auf einem billigeren, aber langsameren Speicher gehostet werden. Offensichtlich ist in diesem Fall der gruppierte Schlüssel (Datum) nicht der Primärschlüssel (Transaktions-ID). Die Trennung der beiden wird durch die Skalierungsanforderungen bestimmt, da der Abfrageoptimierer erkennen kann, dass die Abfragen nur an der aktuellen Partition interessiert sind und nicht einmal die historischen Partitionen betrachten.
-
Verarbeitung im FIFO-Warteschlangenstil. In diesem Fall hat die Tabelle zwei Hotspots:das Ende, an dem Einfügungen auftreten (enqueue), und den Kopf, an dem gelöscht wird (dequeue). Der gruppierte Schlüssel muss dies berücksichtigen und die Tabelle so organisieren, dass die End- und Kopfposition auf der Festplatte physisch getrennt sind, um eine Parallelität zwischen Enqueue und Dequeue zu ermöglichen, z. B. durch Verwendung eines Enqueue-Bestellschlüssels. In rein Warteschlangen ist dieser gruppierte Schlüssel der einzige Schlüssel, da es keinen Primärschlüssel in der Tabelle gibt (sie enthält Nachrichten , nicht Entitäten ). Aber meistens ist die Warteschlange nicht rein, sie fungiert auch als Speicher für die Entitäten und die Linie zwischen der Warteschlange und die Tabelle ist verschwommen. In diesem Fall gibt es auch einen Primärschlüssel, der nicht der Clusterschlüssel sein kann:Entitäten können erneut in die Warteschlange eingereiht werden, wodurch sich der Wert des Clusterschlüssels in der Reihenfolge der Einreihung ändert, aber sie können den Primärschlüsselwert nicht ändern. Das Nichtsehen der Trennung ist der Hauptgrund, warum benutzertabellengestützte Warteschlangen so notorisch schwer zu bekommen und mit Deadlocks durchsetzt sind:weil Enqueue und Dequeue verschachtelt durch die Tabelle erfolgen, anstatt am Ende und am Kopf der Warteschlange lokalisiert zu sein.
-
Korrelierte Verarbeitung. Wenn die Anwendung gut entworfen ist, partitioniert sie die Verarbeitung korrelierter Elemente zwischen ihren Worker-Threads. Zum Beispiel ist ein Prozessor so konzipiert, dass er 8 Worker-Threads hat (sagen wir, um den 8 CPUs auf dem Server zu entsprechen), sodass die Prozessoren die Daten untereinander partitionieren, z. Worker 1 nimmt nur Konten mit den Namen A bis E auf, Worker 2 F bis J usw. In solchen Fällen sollte die Tabelle tatsächlich nach dem Kontonamen gruppiert werden (oder nach einem zusammengesetzten Schlüssel, der ganz links den ersten Buchstaben des Kontonamens hat). damit Arbeiter ihre Abfragen und Aktualisierungen in der Tabelle lokalisieren. Ein solcher Tisch hätte 8 unterschiedliche Hotspots um den Bereich herum, auf den sich jeder Arbeiter derzeit konzentriert, aber das Wichtigste ist, dass sie sich nicht überlappen (keine Blockierung). Diese Art von Design ist bei OLTP-Designs mit hohem Durchsatz und in TPCC-Benchmark-Ladevorgängen weit verbreitet, wo sich diese Art der Partitionierung auch im Speicherort der in den Pufferpool (NUMA-Lokalität) geladenen Seiten widerspiegelt, aber ich schweife ab.
Überlegungen zur Speicherung . Die Breite des gruppierten Schlüssels hat große Auswirkungen auf die Lagerung der Tabelle. Zum einen belegt der Schlüssel Platz in jeder Nicht-Blatt-Seite des b-Baums, sodass ein großer Schlüssel mehr Platz einnehmen wird. Zweitens und oft noch wichtiger ist, dass der geclusterte Schlüssel von jedem nicht geclusterten Schlüssel als Suchschlüssel verwendet wird, also every Ein nicht gruppierter Schlüssel muss die volle Breite des gruppierten Schlüssels für jede Zeile speichern. Das macht große Cluster-Schlüssel wie varchar(256) und Guids zu einer schlechten Wahl für Cluster-Index-Schlüssel.
Auch die Wahl des Schlüssels wirkt sich auf die Fragmentierung des Cluster-Index aus, was sich manchmal drastisch auf die Leistung auswirkt.
Diese beiden Kräfte können manchmal antagonistisch sein, da das Datenzugriffsmuster einen bestimmten großen gruppierten Schlüssel erfordert, der Speicherprobleme verursacht. In solchen Fällen ist natürlich ein Ausgleich gefragt, aber es gibt keine Zauberformel. Sie messen und testen, um den optimalen Punkt zu erreichen.
Was machen wir also aus all dem? Betrachten Sie immer zuerst den gruppierten Schlüssel, der auch der Primärschlüssel der Form entity_id IDENTITY(1,1) NOT NULL
ist . Trennen Sie die beiden und organisieren Sie die Tabelle entsprechend (z. B. Partitionieren nach Datum), wenn es angebracht ist.