Ist es möglich, die Abfrageleistung in einer engen Tabelle mit Millionen von Zeilen zu erhöhen?

14

Ich habe eine Abfrage, die derzeit durchschnittlich 2500 ms in Anspruch nimmt. Mein Tisch ist sehr eng, aber es gibt 44 Millionen Zeilen. Welche Möglichkeiten habe ich, um die Leistung zu verbessern, oder ist dies so gut wie es nur geht?

Die Abfrage

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'; 

Der Tisch

CREATE TABLE [dbo].[Heartbeats](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [DeviceID] [int] NOT NULL,
    [IsPUp] [bit] NOT NULL,
    [IsWebUp] [bit] NOT NULL,
    [IsPingUp] [bit] NOT NULL,
    [DateEntered] [datetime] NOT NULL,
 CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Der Index

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] 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]

Würde das Hinzufügen zusätzlicher Indizes helfen? Wenn ja, wie würden sie aussehen? Die aktuelle Leistung ist akzeptabel, da die Abfrage nur gelegentlich ausgeführt wird. Ich frage mich jedoch, ob ich etwas tun kann, um dies zu beschleunigen.

AKTUALISIEREN

Wenn ich die Abfrage ändere, um einen Force-Index-Hinweis zu verwenden, wird die Abfrage in 50 ms ausgeführt:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 

Das Hinzufügen einer richtig selektiven DeviceID-Klausel trifft auch den Bereich von 50 ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;

Wenn ich ORDER BY [DateEntered], [DeviceID]zur ursprünglichen Abfrage hinzufüge , bin ich im Bereich von 50 ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Diese verwenden alle den Index, den ich erwartet hatte (CommonQueryIndex). Ich nehme an, meine Frage lautet jetzt: Gibt es eine Möglichkeit, die Verwendung dieses Index für Abfragen wie diese zu erzwingen? Oder ist die Größe meines Tisches zu groß und ich muss nur einen ORDER BYoder einen Hinweis verwenden?

Nate
quelle
Ich denke, Sie könnten einen weiteren nicht geclusterten Index für "DateEntered" hinzufügen, der die Leistung etwas mehr verbessern würde
Praveen
@Praveen Wäre es im Grunde das gleiche wie mein bestehender Index? Muss ich etwas Besonderes tun, da es zwei Indizes für dasselbe Feld gibt?
Nate
@Nate, da die Tabelle Heartbeat heißt und es sich um 44 Millionen Datensätze handelt, nehme ich an, Sie haben schwere Einfügungen in dieser Tabelle? Mit der Indizierung können Sie nur einen Deckungsindex hinzufügen, um die Geschwindigkeit zu erhöhen. Wie Sie bereits erwähnt haben, verwenden Sie diese Abfrage nur gelegentlich. Ich rate jedoch dringend davon ab, wenn Sie schwere Einfügungen vornehmen. Es verdoppelt im Grunde Ihre Insert-Last. Laufen Sie auf Enterprise Edition?
Edward Dortland
Ich habe festgestellt, dass Sie die Geräte-ID in Ihrem NC-Index haben. Ist es möglich, das in Ihre where-Klausel aufzunehmen? Und würde das die Ergebnismenge unter den Schwellenwert senken? <35.000 Datensätze (ohne die Top 1000-Klausel).
Edward Dortland
1
Letzte Frage: Fügen Sie immer in der Reihenfolge des eingegebenen Datums ein? Oder können diese nicht in Ordnung sein, da Geräte möglicherweise asynchron voneinander arbeiten. Möglicherweise versuchen Sie, den Clustered-Index in die DateEntered-Spalte zu ändern. Ihre Urlaubsseiten in Ihrem Clustered-Index umfassen jetzt 445 Seiten. Das würde sich verdoppeln, wenn Sie von einem int zu einem datetime wechseln würden. Aber in diesem Fall ist das vielleicht nicht so schlimm.
Edward Dortland

Antworten:

13

Warum das Optimierungsprogramm nicht für Ihren ersten Index verwendet wird:

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] 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]

Ist eine Frage der Selektivität der Spalte [DateEntered].

Sie haben uns mitgeteilt, dass Ihre Tabelle 44 Millionen Zeilen enthält. Die Zeilengröße ist:

4 Byte für die ID, 4 Byte für die Geräte-ID, 8 Byte für das Datum und 1 Byte für die 4-Bit-Spalten. Das sind 17 Bytes + 7 Bytes Overhead für (Tags, Null-Bitmap, var col offset, col count) insgesamt 24 Bytes pro Zeile.

Das würde sich nur schwer auf 140.000 Seiten übertragen lassen. Um diese 44 Millionen Zeilen zu speichern.

Jetzt kann der Optimierer zwei Dinge tun:

  1. Es könnte die Tabelle scannen (Clustered-Index-Scan)
  2. Oder es könnte Ihren Index verwenden. Für jede Zeile in Ihrem Index müsste dann eine Lesezeichensuche im Clustered-Index durchgeführt werden.

Ab einem bestimmten Zeitpunkt wird es nur noch teurer, alle diese einzelnen Suchvorgänge im Clustered-Index für jeden Indexeintrag durchzuführen, der in Ihrem Nicht-Clustered-Index gefunden wird. Der Schwellenwert dafür ist im Allgemeinen die Gesamtzahl der Suchvorgänge, die 25% bis 33% der gesamten Seitenzahl der Tabelle überschreiten sollte.

Also in diesem Fall: 140k / 25% = 35000 Zeilen 140k / 33% = 46666 Zeilen.

(@RBarryYoung, 35k ist 0,08% der gesamten Zeilen und 46666 ist 0,10%, also denke ich, dass hier die Verwirrung war)

Wenn Ihre where-Klausel also irgendwo zwischen 35000 und 46666 Zeilen ergibt (dies befindet sich unter der obersten Klausel!), Wird Ihr nicht geclusterter Index höchstwahrscheinlich nicht verwendet und der geclusterte Index-Scan wird verwendet.

Die einzigen zwei Möglichkeiten, dies zu ändern, sind:

  1. Machen Sie Ihre where-Klausel selektiver. (wenn möglich)
  2. Löschen Sie das * und wählen Sie nur einige Spalten aus, damit Sie einen Deckungsindex verwenden können.

Jetzt können Sie sicher einen Deckungsindex erstellen, auch wenn Sie ein select * verwenden. Wie auch immer, das nur einen enormen Aufwand für Ihre Einfügungen / Aktualisierungen / Löschungen verursacht. Wir müssten mehr über Ihre Arbeitslast (Lesen gegen Schreiben) wissen, um sicherzustellen, dass dies die beste Lösung ist.

Beim Wechsel von datetime zu smalldatetime wird die Größe des Clustered-Index um 16% und die Größe des nicht geclusterten Index um 24% verringert.

Edward Dortland
quelle
Die Scan-Schwelle ist normalerweise viel niedriger als diese (10% oder noch niedriger). Da der Bereich jedoch ein einzelner Tag von vor über einem Jahr ist, sollte er nicht einmal diese Schwelle erreichen. Und ein Clustered-Index-Scan ist keine Selbstverständlichkeit, da ein Deckungsindex hinzugefügt wurde. Da dieser Index die WHERE-Klausel SARG-fähig macht, sollte er bevorzugt werden.
RBarryYoung
@RBarryYoung Ich habe versucht zu erklären, warum der nicht gruppierte Index für [EnteredDate], [DeviceID] überhaupt nicht verwendet wurde. In Bezug auf die Schwelle denke ich, dass wir beide einer Meinung sind, ich spreche nur aus einer Seitenperspektive. Ich werde meine Antwort ändern, um es klarer zu machen.
Edward Dortland
Die Antwort wurde geändert, um klarer zu machen, was ich antwortete. Ich kann nicht erklären, warum der von @RBarryYoung vorgeschlagene Titelindex nicht verwendet wird. Ich habe es gerade hier an einer Million Zeilen getestet und das Optimierungsprogramm anhand des Deckungsindex.
Edward Dortland
Vielen Dank für eine sehr umfassende Antwort, macht sehr viel Sinn. In Bezug auf die Arbeitslast enthält die Tabelle 150-300 Einfügungen pro 5-Minuten-Zeitraum und einige Lesevorgänge pro Tag für Berichtszwecke.
Nate
Der Overhead für den Deckungsindex ist nicht wirklich signifikant, da es sich um eine schmale Tabelle handelt und die "Deckung" nur eine Ergänzung zu dem bereits vorhandenen Index ist, der bereits den größten Teil der Zeile enthielt.
RBarryYoung
8

Gibt es einen bestimmten Grund für die Clusterbildung Ihrer PK? Viele Leute tun dies, weil die Standardeinstellung so ist, oder weil sie der Meinung sind, dass PKs zu Clustern zusammengefasst werden müssen. Nein so Clustered-Indizes eignen sich normalerweise am besten für Bereichsabfragen (wie diese) oder für den Fremdschlüssel einer untergeordneten Tabelle.

Ein Clustering-Index bewirkt, dass alle Daten gebündelt werden, da die Daten auf den Blattknoten des Cluster-B-Baums gespeichert sind. Unter der Annahme, dass Sie nicht nach einem zu großen Bereich fragen, weiß der Optimierer genau, welcher Teil des b-Baums die Daten enthält, und er muss keinen Zeilenbezeichner finden und dann dorthin springen, wo die Daten sind ist (wie es beim Umgang mit einem NC-Index der Fall ist). Was ist ein zu großer Bereich? Ein lächerliches Beispiel wäre, Daten für 11 Monate aus einer Tabelle anzufordern, die nur ein Jahr lang Datensätze enthält. Das Abrufen von Daten für einen Tag sollte kein Problem darstellen, vorausgesetzt, Ihre Statistiken sind aktuell. (Der Optimierer kann jedoch Probleme bekommen, wenn Sie nach den gestrigen Daten suchen und die Statistiken drei Tage lang nicht aktualisiert haben.)

Da Sie eine "SELECT *" - Abfrage ausführen, muss die Engine alle Spalten in der Tabelle zurückgeben (auch wenn jemand eine neue Spalte hinzufügt, die Ihre App derzeit nicht benötigt), also einen abdeckenden Index oder einen Index mit eingeschlossenen Spalten wird nicht viel helfen, wenn überhaupt. (Wenn Sie jede Spalte aus der Tabelle in einen Index aufnehmen, liegt ein Fehler vor.) Wahrscheinlich ignoriert der Optimierer diese NC-Indizes.

Also, was ist zu tun?

Mein Vorschlag wäre, den NC-Index zu löschen, die Clustered-PK in Nonclustered zu ändern und einen Clustered-Index für [DateEntered] zu erstellen. Einfacher ist besser, bis das Gegenteil bewiesen ist.

darin straße
quelle
Vorausgesetzt, die Zeilen werden in aufsteigender Reihenfolge eingefügt, ist dies die einfachste Antwort. Wenn Sie sie jedoch in nichtlinearer Reihenfolge einfügen, wird sie fragmentiert.
Kirk Broadhurst
Das Hinzufügen von Daten zu einer B-Tree-Struktur führt zu einem Gleichgewichtsverlust. Selbst wenn Sie Zeilen in Clusterreihenfolge hinzufügen, verlieren die Indizes das Gleichgewicht. Durch die Neuindizierung von Tabellen wird die Fragmentierung beseitigt, und jeder DBA weist Sie darauf hin, dass Tabellen neu indiziert werden müssen, nachdem einer Tabelle "genügend" Daten hinzugefügt wurden. (Die Definition von "genug" könnte diskutiert werden, oder "wann" könnte eine Diskussion sein.) Ich sehe nichts in der Frage, die besagt, dass eine erneute Indizierung aus irgendeinem Grund nicht möglich ist.
strait
4

Solange Sie das "*" haben, kann ich mir nur vorstellen, dass es einen großen Unterschied macht, wenn Sie Ihre Indexdefinition folgendermaßen ändern:

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)INCLUDE (ID, IsWebUp, IsPingUp, IsPUp)
 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]

Wie in den Kommentaren erwähnt, sollte dieser Index verwendet werden. Wenn dies nicht der Fall ist, können Sie ihn entweder mit einem ORDER BY- oder einem Indexhinweis überzeugen.

RBarryYoung
quelle
Ich habe es gerade ausprobiert und bin immer noch an der gleichen Stelle. 2500 ms warten auf Serverantwort und 10 ms Client-Verarbeitungszeit.
Nate
Veröffentlichen Sie den Abfrageplan.
RBarryYoung
Sieht so aus, als würde der Clustered Index verwendet. (SELECT Kosten: 0% <- Top Kosten: 20% <- Clustered Index Scan PK_Heartbeats Kosten: 80%)
Nate
Ja, das stimmt nicht, irgendetwas wirft die Statistiken / den Optimierer aus. Fügen Sie einen Hinweis hinzu, um die Verwendung des neuen Index zu erzwingen.
RBarryYoung
@ Max Vernon: Vielleicht, aber das sollte auf dem Abfrageplan markiert worden sein.
RBarryYoung
3

Ich würde das etwas anders sehen.

  • Ja, ich weiß, es ist ein alter Thread, aber ich bin fasziniert.

Ich würde die datetime-Spalte ausgeben und sie in eine int-Spalte ändern. Lassen Sie sich eine Nachschlagetabelle anzeigen oder konvertieren Sie Ihr Datum.

Den gruppierten Index sichern - belassen Sie ihn als Heap und erstellen Sie einen nicht gruppierten Index für die neue INT-Spalte, die das Datum darstellt. dh heute wäre 20121015. Diese Reihenfolge ist wichtig. Abhängig davon, wie häufig Sie die Tabelle laden, sollten Sie diesen Index in der DESC-Reihenfolge erstellen. Die Wartungskosten sind höher und Sie möchten einen Füllfaktor oder eine Partitionierung einführen. Die Partitionierung würde auch dazu beitragen, die Laufzeit zu verkürzen.

Wenn Sie SQL 2012 verwenden können, versuchen Sie es schließlich mit SEQUENCE - es übertrifft identity () für Einfügungen.

Jeremy Lowell
quelle
Interessante Lösung. Obwohl es aus meiner Frage nicht ersichtlich ist, ist der Zeitanteil von DateTime sehr wichtig. Im Allgemeinen frage ich basierend auf dem Datum ab, um bestimmte Zeiten in diesem Zeitraum zu überprüfen. Wie würden Sie diese Lösung anpassen, um dies zu berücksichtigen?
Nate
Behalten Sie in diesem Fall die Spalte datetime bei, und fügen Sie die Spalte int für date hinzu (da Ihr Bereich auf dem Datumselement und nicht auf dem Zeitelement basiert). Sie können auch den Datentyp TIME verwenden und dann die Zeit effektiv vom Datum trennen. Auf diese Weise ist Ihr Daten-Footprint kleiner und Sie haben immer noch das Time-Element der Spalte.
Jeremy Lowell
1
Ich bin nicht sicher, warum ich dies früher verpasst habe, aber ich verwende die Zeilenkomprimierung sowohl für den Clustered-Index als auch für den Nicht-Clustered-Index. Ich habe gerade einen kurzen Test mit Ihrer Tabelle durchgeführt und Folgendes festgestellt: Ich habe einen Datensatz (5,8 Millionen Zeilen) in der oben definierten Tabelle erstellt. Ich habe den gruppierten und nicht gruppierten Index komprimiert (Zeile). Die Anzahl der logischen Lesevorgänge wurde basierend auf Ihrer genauen Abfrage von 2.074 auf 1.433 verringert. Das ist ein deutlicher Rückgang, und ich bin zuversichtlich, dass Ihnen das allein helfen würde - und das Risiko ist sehr gering.
Jeremy Lowell