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 BY
oder einen Hinweis verwenden?
Antworten:
Warum das Optimierungsprogramm nicht für Ihren ersten Index verwendet wird:
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:
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:
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.
quelle
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.
quelle
Solange Sie das "*" haben, kann ich mir nur vorstellen, dass es einen großen Unterschied macht, wenn Sie Ihre Indexdefinition folgendermaßen ändern:
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.
quelle
Ich würde das etwas anders sehen.
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.
quelle