Feldreihenfolge in einer zusammengesetzten Indexreihenfolge mit Feldern mit hoher Selektivität und niedriger Selektivität

11

Ich habe eine SQL Server-Tabelle mit über 3 Milliarden Zeilen. Eine meiner Abfragen dauert sehr lange, daher überlege ich, sie zu optimieren. Die Abfrage sieht folgendermaßen aus:

SELECT [Enroll_Date]
      ,Count(*) AS [Record #]
      ,Count(Distinct UserID) AS [User #]
  FROM UserTable
  GROUP BY [Enroll_Date]

Das [Enroll_Date] ist eine Spalte mit niedriger Selektivität mit weniger als 50 möglichen Werten, während die Spalte UserID eine Spalte mit hoher Selektivität mit mehr als 200 Millionen unterschiedlichen Werten ist. Aufgrund meiner Forschung glaube ich, dass ich für diese beiden Spalten einen nicht gruppierten zusammengesetzten Index erstellen sollte, und theoretisch sollte die Spalte mit hoher Selektivität die erste Spalte sein. Aber ich bin mir in meinem Fall nicht sicher, ob das funktionieren würde, da ich die Spalte mit niedriger Selektivität in der group by-Klausel verwende.

Diese Tabelle hat keinen Clustered-Index.

Denker
quelle
Können Sie den eigentlichen Ausführungsplan xml posten (Pastebin verwenden und hier verlinken)? Welche Version von SQL Server verwenden Sie?
Kin Shah
3
Der Index mit der hochselektiven Spalte zuerst ist für die spezifische Abfrage unbrauchbar.
Ypercubeᵀᴹ
Es wird empfohlen, die Spalte mit der höheren Selektivität (normalerweise) als erste Schlüsselspalte in einem Index zu verwenden. In diesem Szenario hilft es Ihnen, wie Sie vermutet haben, überhaupt nicht. Möglicherweise benötigen Sie zwei Indizes! Was passiert, wenn Sie zuerst register_date und dann user_id verwenden?
Paulbarbin

Antworten:

12

Als Alternative zur Lösung von @ AaronBertrand (wenn Sie keine indizierte Ansicht erstellen können oder möchten) würde ich Ihnen empfehlen, einen Index für zu erstellen (Enroll_Date, UserID). Wenn diese Art von Frage in Ihrer Tabelle sehr häufig vorkommt, sollte dies wahrscheinlich sogar Ihr Clustered-Index sein.

Ich würde im Allgemeinen keine Indizes mit hoher Selektivität als allgemeine "Best Practice" empfehlen, sondern vielmehr prüfen, welcher Index Ihrer Abfrage die beste Leistung verleiht.

Ein Index für (Enroll_Date, UserID)gibt Ihrer Abfrage einen hochoptimierten, nicht blockierenden Abfrageplan mit Stream-Aggregaten.

Aggregierten Abfrageplan streamen

"Nicht blockierend" bedeutet in diesem Zusammenhang, dass die Abfrage keine signifikanten Datenmengen puffern muss (wie zum Beispiel eine Sortierung oder ein Hash-Aggregat), was bedeutet, dass (a) sofort Zeilen zurückgegeben werden und b) verbraucht praktisch keinen Arbeitsspeicher.

Daniel Hutmacher
quelle
Witzig, 4 Sekunden auseinander und die gleiche Antwort.
usr
11

Aaron Antwort ist eine großartige Lösung. Ich werde die Frage beantworten, vorausgesetzt, Sie möchten diesen Ansatz nicht wählen.

Die Abfrage, die Sie gepostet haben, wird normalerweise ausgeführt, indem Sie zuerst gruppieren (Enroll_Date, UserID)und dann wieder ein (Enroll_Date). Diese Optimierung ist neu in SQL Server 2012. Sie wird im Falle einer einzelnen wirksam COUNT DISTINCT.

Ein Index für diese beiden Spalten in der angegebenen Reihenfolge reicht (Enroll_Date, UserID)aus, um einen effizienten Plan zu erhalten, der einen Index-Scan in zwei aufeinanderfolgende Stream-Aggregate unterteilt. Die entgegengesetzte Reihenfolge würde diesen Plan nicht ermöglichen.

Verwenden Sie daher die Reihenfolge (Enroll_Date, UserID). Sie haben hier keine Wahl.

usr
quelle
5 Sekunden auseinander und die gleiche Lösung. Gut gespielt, Sir. :)
Daniel Hutmacher
@ DanielHutmacher OMG, werden wir es schaffen, unsere Beiträge zum dritten Mal fast zusammenzubringen?! +1 für dich! Wie könnte ich eine identische Antwort nicht positiv bewerten?
usr
Panne in der Matrix. :)
Daniel Hutmacher
Vielen Dank. Ich erstelle den Index und werde die Verbesserung veröffentlichen, nachdem sie abgeschlossen ist. Die Serverversion ist Microsoft SQL Server 2008 R2 unter AWS, aber ich denke, es ist immer noch die einzige Möglichkeit, unabhängig davon.
Thinkinger
@ Thinkinger für den Fall, dass Sie Aarons Ansatz nicht akzeptieren, haben Sie eine schwierige Wahl :)
usr
11

Klingt nach einem idealen Szenario für eine indizierte Ansicht, mit der Sie Berechnungen und Aggregate zum Zeitpunkt des Schreibens anstelle der Abfragezeit bezahlen können.

CREATE VIEW dbo.MyIndexedView
WITH SCHEMABINDING
AS 
  SELECT Enroll_Date, UserID, RawCount = COUNT_BIG(*)
  FROM dbo.UserTable
  GROUP BY Enroll_Date, UserID;
GO

CREATE UNIQUE CLUSTERED INDEX CIX_miv ON dbo.MyIndexedView(Enroll_Date, UserID);

Das Erstellen dauert einige Zeit und erfordert natürlich eine Wartung aller DML-Vorgänge, genau wie ein Index für die Basistabelle.

Jetzt wäre die Abfrage für diese Ansicht ziemlich ähnlich - jede Zeile in der Ansicht stellt jetzt eine eigene Benutzer- / Datumskombination dar, sodass diese Zahl durch einen einzelnen COUNT (*) berechnet werden kann, während die Gesamtzahl der Zeilen in der Basistabelle gleich ist bereits teilweise für Sie aggregiert, jetzt müssen Sie sie nur noch mit SUM pro Datum addieren:

SELECT Enroll_Date, 
  [Record #] = SUM(RawCount),
  [User #] = COUNT(*)
FROM dbo.MyIndexedView WITH (NOEXPAND)
GROUP BY Enroll_Date; 

NOEXPAND-Hinweis hinzugefügt, nachdem dies und das gespeichert wurde .

Ich kann Ihnen ohne Zweifel sagen, dass diese Abfrage schneller ist als Ihre aktuelle Abfrage (aber nicht um wie viel), außer in dem seltenen Fall, dass Sie genau einen Benutzer für jedes Datum haben (in diesem Fall wird dieselbe Datenmenge vorhanden sein) zu lesen) und die uns bekannten Spalten sind die einzigen Spalten im Index der Basistabelle. Ob diese Leistungssteigerung zum Zeitpunkt des Lesens die zusätzliche Arbeit wert ist, die sich auf den Schreibanteil Ihrer Arbeitslast auswirkt, können wir Ihnen nicht sagen - Sie müssen sie testen, um den Kompromiss zu messen (kein Index ist frei).

Und wenn Sie häufig dieselben allgemeinen WHERE-Klauseln für Enroll_Date für bestimmte, genau definierte Bereiche verwenden (z. B. das aktuelle Quartal oder Jahr bis heute), können Sie übereinstimmende gefilterte Indizes hinzufügen, die diese E / A noch weiter reduzieren (aber es gibt immer eine Abtausch).

Sie können auch einen Clustered-Index für die Basistabelle erstellen. Dies scheint nicht einer der sehr seltenen Anwendungsfälle zu sein, die von einem Haufen profitieren.

Aaron Bertrand
quelle
Ich habe es gerade mit unserer IT bestätigt und es scheint, dass ich diese Art von Ansicht nicht erstellen kann. Aber schätzen Sie Ihren Rat trotzdem und er wird anderen helfen, die ihn nutzen können.
Thinkinger
1
Ist Ihre IT der Ansicht, dass es einen signifikanten Unterschied zwischen einer indizierten Ansicht und zusätzlichen oder unterschiedlichen Indizes in der Basistabelle gibt? Nicht kämpferisch, nur neugierig, weil viele Leute falsche Vorstellungen über indizierte Ansichten haben. Ich stelle sie mir gerne als zusätzlichen, dünneren Clustered-Index für die Tabelle vor, aber mit weniger Zeilen.
Aaron Bertrand
@Thinkinger auch, indizierte Ansichten sind nicht nur EE. Indizierte Sicht Anpassung ist EE-only. Sie können sie mit NOEXPAND direkt anvisieren.
usr