Das Ausdrücken der Abfrage mit einer anderen Syntax kann manchmal dazu beitragen, dem Optimierer den Wunsch zu übermitteln, einen nicht gruppierten Index zu verwenden. Sie sollten das Formular unten finden, um den gewünschten Plan zu erhalten:
SELECT
[ID],
[DeviceID],
[IsPUp],
[IsWebUp],
[IsPingUp],
[DateEntered]
FROM [dbo].[Heartbeats]
WHERE
[ID] IN
(
-- Keys
SELECT TOP (1000)
[ID]
FROM [dbo].[Heartbeats]
WHERE
[DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
AND [DateEntered] < CONVERT(datetime, '2011-08-31', 121)
);
Vergleichen Sie diesen Plan mit dem Plan, der erstellt wurde, als der nicht gruppierte Index mit einem Hinweis erzwungen wurde:
SELECT TOP (1000)
*
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE
[DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
Die Pläne sind im Wesentlichen identisch (eine Schlüsselsuche ist nichts anderes als eine Suche im Clustered-Index). Beide Planformen führen immer nur eine Suche für den nicht gruppierten Index und maximal 1000 Suchvorgänge für den gruppierten Index durch.
Der wichtige Unterschied liegt in der Position des Top-Operators. Zwischen den beiden Suchvorgängen positioniert, verhindert der Top, dass das Optimierungsprogramm die beiden Suchvorgänge durch einen logisch äquivalenten Scan des Clustered-Index ersetzt. Das Optimierungsprogramm ersetzt Teile eines logischen Plans durch gleichwertige relationale Operationen. Top ist kein relationaler Operator, daher verhindert das Umschreiben die Umwandlung in einen Clustered-Index-Scan. Wenn das Optimierungsprogramm den Operator "Top" neu positionieren könnte, würde es den Scan aufgrund der Funktionsweise der Kostenschätzung immer noch dem Suchen + Nachschlagen vorziehen.
Kalkulation von Scans und Suchen
Auf einem sehr hohen Niveau ist das Kostenmodell des Optimierers für Scans und Suchvorgänge recht einfach: Es schätzt, dass 320 zufällige Suchvorgänge dasselbe kosten wie das Lesen von 1350 Seiten in einem Scan. Dies hat wahrscheinlich wenig Ähnlichkeit mit den Hardwarefunktionen eines bestimmten modernen E / A-Systems, funktioniert aber als praktisches Modell recht gut.
Das Modell geht auch von einer Reihe vereinfachender Annahmen aus. Eine der wichtigsten ist, dass angenommen wird, dass jede Abfrage ohne Daten- oder Indexseiten beginnt, die sich bereits im Cache befinden. Die Implikation ist, dass jede E / A zu einer physischen E / A führt - obwohl dies in der Praxis selten der Fall ist. Selbst bei einem kalten Cache bedeutet das Vorabrufen und Vorauslesen, dass die benötigten Seiten tatsächlich ziemlich wahrscheinlich im Speicher sind, wenn der Abfrageprozessor sie benötigt.
Eine weitere Überlegung ist, dass die erste Anforderung für eine Zeile, die sich nicht im Speicher befindet, dazu führt, dass die gesamte Seite von der Festplatte abgerufen wird. Nachfolgende Anforderungen für Zeilen auf derselben Seite verursachen höchstwahrscheinlich keine physischen E / A-Vorgänge. Das Kalkulationsmodell enthält zwar Logik, um solche Effekte zu berücksichtigen, ist aber nicht perfekt.
All diese Dinge (und mehr) bedeuten, dass das Optimierungsprogramm tendenziell früher zu einem Scan wechselt, als dies wahrscheinlich der Fall sein sollte. Zufällige E / A-Vorgänge sind nur dann "viel teurer" als "sequenzielle" E / A-Vorgänge, wenn sich eine physische Operation ergibt. Der Zugriff auf Seiten im Speicher ist in der Tat sehr schnell. Selbst wenn ein physischer Lesevorgang erforderlich ist, führt ein Scan aufgrund der Fragmentierung möglicherweise überhaupt nicht zu sequenziellen Lesevorgängen, und Suchvorgänge können so angeordnet werden, dass das Muster im Wesentlichen sequenziell ist. Hinzu kommt, dass die sich ändernden Leistungsmerkmale moderner E / A-Systeme (insbesondere von Festkörpern) und das Ganze sehr wackelig aussehen.
Reihenziele
Das Vorhandensein eines Top-Operators in einem Plan verändert den Kalkulationsansatz. Das Optimierungsprogramm ist intelligent genug, um zu wissen, dass zum Ermitteln von 1000 Zeilen bei einem Scan wahrscheinlich nicht der gesamte Clustered-Index durchsucht werden muss. Es kann beendet werden, sobald 1000 Zeilen gefunden wurden. Er legt ein Zeilenziel von 1000 Zeilen für den Operator "Oben" fest und verwendet statistische Informationen, um von dort aus abzuschätzen, wie viele Zeilen von der Zeilenquelle voraussichtlich benötigt werden (in diesem Fall ein Scan). Über die Einzelheiten dieser Berechnung habe ich hier geschrieben .
Die Bilder in dieser Antwort wurden mit SQL Sentry Plan Explorer erstellt .