Ein Index ist vorhanden, trotzdem liest eine Abfrage einen großen Teil der Tabelle. Häufig liegt das an der Suchbedingung: Eine Funktion verarbeitet jeden Spaltenwert, ein Parameter hat den falschen Datentyp oder eine Textsuche beginnt mit einem Platzhalter. Genau hier hilft es, SARGability in SQL Server zu verstehen.
In diesem Artikel vergleichst du problematische und indexfreundliche Abfragen für Datumsfilter, Funktionen auf Spalten, implizite Typumwandlungen und LIKE. Ein vollständiges Testskript erzeugt passende Beispieldaten und liefert tatsächliche Ausführungspläne sowie Ausgaben von SET STATISTICS IO und SET STATISTICS TIME.
Entscheidend ist dabei die tatsächlich erledigte Arbeit. Die Beschriftung Index Seek allein zeigt noch nicht, ob eine Abfrage effizient ist.
Was bedeutet SARGability in SQL Server?
SARGable steht für „Search ARGument Able“. Gemeint ist eine Suchbedingung, die SQL Server für einen gezielten Zugriff über einen geeigneten Index verwenden kann. Bei den folgenden Beispielen geht es um gewöhnliche zeilenbasierte B-Baum-Indizes.
Stell dir einen nach Bestelldatum sortierten Index vor. Mit einer Bedingung wie „ab dem 15. Mai, aber vor dem 16. Mai“ kann SQL Server einen zusammenhängenden Bereich bestimmen. Wird dagegen jeder gespeicherte Zeitpunkt zunächst in einen formatierten Text umgewandelt, lässt sich diese Sortierung häufig nicht unmittelbar für die Suche verwenden.
| Suchbedingung | Einordnung |
|---|---|
Bestelldatum >= @Von AND Bestelldatum < @Bis |
Direkter Bereich auf der Datumsspalte |
CONVERT(char(8), Bestelldatum, 112) = '20260515' |
Formatierung der Spaltenwerte erschwert den gezielten Zugriff |
Kundencode = @Code mit passendem Parametertyp |
Direkter Gleichheitsvergleich |
Kundencode LIKE 'K0042%' |
Festes Präfix kann einen Indexbereich begrenzen |
Belegcode LIKE '%-4711' |
Führender Platzhalter liefert keinen festen Anfang für den ursprünglichen Index |
Eine indexfreundliche Bedingung garantiert keinen Index Seek. Dafür müssen unter anderem Indexschlüssel, Datenverteilung und erwartete Treffermenge passen. Wenn eine Abfrage einen großen Anteil der Daten benötigt, kann ein Scan die günstigere Wahl sein.
Die Grundlagen zu geeigneten Schlüsselspalten und abdeckenden Indizes beschreibt Microsoft im Leitfaden zur Indexarchitektur und zum Indexdesign.
Datumsfilter: Zeiträume mit einer offenen Obergrenze suchen
Problematisch: Das Datum für den Vergleich formatieren
Für alle Bestellungen eines Tages erscheint eine Umwandlung in eine Datumszeichenfolge zunächst bequem:
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE CONVERT(char(8), Bestelldatum, 112) = '20260515';
Der Stil 112 erzeugt die Darstellung yyyymmdd. Fachlich beschreibt die Abfrage den gewünschten Tag. Für den Zugriff ist sie jedoch ungünstig: Der vorhandene Index ist nach Bestelldatum geordnet, nicht nach dem Ergebnis dieser Textformatierung.
Verbessert: Die Datumsspalte direkt eingrenzen
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Bestelldatum >= '20260515'
AND Bestelldatum < '20260516';
Die Untergrenze gehört zum Ergebnis, die Obergrenze nicht. Dadurch erfasst die Abfrage sämtliche Uhrzeiten des 15. Mai. Die verwendeten Datumszeichenfolgen im Format yyyymmdd sind für diese Konvertierung eindeutig.
In einer Anwendung übergibst du die Grenzen vorzugsweise als typisierte Parameter. Für die im Test verwendete Spalte vom Typ datetime2(0) sieht das beispielsweise so aus:
DECLARE @Von datetime2(0) = '2026-05-15T00:00:00';
DECLARE @Bis datetime2(0) = DATEADD(day, 1, @Von);
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Bestelldatum >= @Von
AND Bestelldatum < @Bis;
Die Funktion DATEADD berechnet hier eine Grenze aus einem Parameter. Sie wird nicht auf die gespeicherte Datumsspalte angewendet. Funktionen sind also nicht grundsätzlich problematisch; ihre Position und die mögliche Umsetzung durch den Optimierer sind entscheidend.
Vermeide für Tagesfilter eine künstliche letzte Uhrzeit wie 23:59:59.997. Diese Grenze hängt von der Genauigkeit des Datentyps ab. Auch BETWEEN mit Mitternacht des Folgetages wäre falsch, weil BETWEEN beide Grenzen einschließt.
Für Monate und Jahre funktioniert dasselbe Muster: Beginn des gewünschten Zeitraums einschließlich, Beginn des nächsten Zeitraums ausschließlich. Weitere Beispiele und Hinweise zu Zeitzonen findest du im Artikel Datum und Zeit in SQL Server: Datentypen, Filter und Zeitzonen.
Ausnahme: CAST auf date kann einen Seek ermöglichen
Die Faustregel „jede Funktion auf einer Spalte verhindert einen Seek“ ist zu pauschal. Bei bestimmten Umwandlungen kann SQL Server selbst einen Suchbereich ableiten. Ein bekanntes Beispiel ist die Umwandlung eines Zeitstempels in date:
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE CAST(Bestelldatum AS date) =
CONVERT(date, '20260515', 112);
Diese Abfrage kann einen Index Seek erhalten. Prüfe trotzdem die tatsächlich gelesenen Zeilen und mögliche zusätzliche Filterbedingungen. Ein vom Optimierer abgeleiteter Bereich muss nicht in jeder Konstellation dieselben Eigenschaften wie ein ausdrücklich formulierter Zeitraum haben.
Das Testskript enthält diesen Vergleich als eigenen Fall. So lässt sich die Ausnahme auf der verwendeten SQL-Server-Version untersuchen.
Funktionen auf Spalten: Den gespeicherten Wert direkt durchsuchen
Problematisch: Ein Präfix mit LEFT abschneiden
Gesucht sind alle Kundencodes, die mit K0042 beginnen:
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE LEFT(Kundencode, 5) = 'K0042';
Der Index enthält die vollständigen Kundencodes. Die Bedingung fordert dagegen einen Vergleich mit einem berechneten Ausschnitt. Ohne eine passende zusätzliche Struktur kann SQL Server dafür viele Indexeinträge lesen und die Funktion auf deren Werte anwenden.
Verbessert: Das Präfix mit LIKE ausdrücken
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Kundencode LIKE 'K0042%';
Für die im Test verwendeten Codes beschreiben beide Abfragen dieselbe Treffermenge. Die zweite Variante nennt einen festen Anfang. SQL Server kann daraus einen Bereich im Index auf Kundencode ableiten.
Ähnliche Fragen stellen sich bei UPPER, LOWER, LTRIM, ISNULL oder Berechnungen wie Preis * 1.19 im Filter. Prüfe jeweils, ob sich die Bedingung ohne Veränderung des Ergebnisses direkt auf der gespeicherten Spalte formulieren lässt.
Entferne solche Funktionen jedoch nicht blind. Bei einer Groß-/Kleinschreibung unterscheidenden Kollation kann das Entfernen von UPPER das Suchverhalten ändern. Ebenso gehören NULL-Behandlung, Leerzeichen und Rundung zur fachlichen Bedeutung einer Abfrage.
Wenn eine berechnete Form dauerhaft benötigt wird, kommt eine indexierte berechnete Spalte infrage. Microsoft erläutert die allgemeinen Probleme und mögliche Abhilfen im Abschnitt zur Untersuchung von SARGability-Problemen.
Implizite Typumwandlungen: Parameter passend deklarieren
Eine Suchbedingung kann auch ohne sichtbaren Funktionsaufruf ungünstig sein. Das passiert beispielsweise, wenn eine Anwendung einen nvarchar-Parameter mit einer varchar-Spalte vergleicht.
Problematisch: Unicode-Parameter für eine varchar-Spalte
Im Test ist Kundencode als varchar(12) mit der Kollation SQL_Latin1_General_CP1_CI_AS definiert. Der folgende Aufruf übergibt einen anders typisierten Parameter:
EXEC sys.sp_executesql
N'SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Kundencode = @Code;',
N'@Code nvarchar(12)',
@Code = N'K004200';
nvarchar hat in SQL Server eine höhere Datentyppriorität als varchar. Dadurch kann im Vergleich eine Umwandlung der Spalte entstehen. Unter der hier bewusst gewählten SQL-Kollation ist das ein typischer Anlass für einen Scan.
Verbessert: Parametertyp und Spaltentyp abstimmen
EXEC sys.sp_executesql
N'SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Kundencode = @Code;',
N'@Code varchar(12)',
@Code = 'K004200';
Für diese auf ASCII-Zeichen beschränkten Kundencodes passt der Parameter nun zur Spalte. Im Ausführungsplan solltest du untersuchen, ob der Vergleich unmittelbar als Suchbedingung auf Kundencode verwendet wird.
Suche bei der problematischen Variante im Plan nach CONVERT_IMPLICIT und gegebenenfalls nach einer Warnung PlanAffectingConvert. Entscheidend ist, welcher Ausdruck umgewandelt wird. Eine Konvertierung eines Parameters ist anders zu beurteilen als eine Konvertierung sämtlicher gelesener Spaltenwerte.
Nicht jede implizite Konvertierung verhindert einen Seek. Das Verhalten hängt unter anderem von den beteiligten Datentypen und der Kollation ab. Eine Warnung ist ein Untersuchungsansatz, noch kein gemessener Leistungsnachteil.
Die Prioritätsregeln stehen in der Microsoft-Dokumentation zur Datentyppriorität. Für Anwendungen lohnt sich zusätzlich ein Blick auf die tatsächlich gesendeten Parameterdefinitionen des Treibers oder ORM.
Die Lösung lautet nicht, Unicode grundsätzlich zu vermeiden. Wenn die Daten Unicode benötigen, müssen Schema und Anwendung das unterstützen. Eine pauschale Umwandlung von Benutzereingaben nach varchar könnte Zeichen verlieren oder Suchergebnisse verändern.
LIKE-Abfragen: Präfix, Endung und Teilzeichenfolge unterscheiden
Ein festes Präfix hilft dem ursprünglichen Index
Eine Suche wie LIKE 'K0042%' hat einen bekannten Anfang. Bei LIKE '%0042%' kann die Zeichenfolge dagegen an beliebiger Stelle stehen. Ein nach vollständigen Kundencodes geordneter Index bietet dafür normalerweise keinen entsprechend engen Suchbereich.
Die beiden Muster sind jedoch keine austauschbaren Optimierungsvarianten: „beginnt mit“ und „enthält“ sind unterschiedliche Anforderungen. Entfernst du das führende Prozentzeichen, können gültige Treffer verschwinden.
Auch ein Präfix garantiert keine kleine Ergebnismenge. Ein Muster wie LIKE 'K%' trifft im späteren Test auf alle Kundencodes zu. Dafür kann ein Scan sinnvoll sein.
Problematisch: Eine Endung im ursprünglichen Wert suchen
Für eine echte Endungssuche bleibt der führende Platzhalter fachlich notwendig:
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Belegcode LIKE '%-4711';
Gesucht sind alle Belegcodes, die mit -4711 enden. Ein gewöhnlicher Index auf Belegcode ist vom Anfang des Wertes aus sortiert und kann die Endung deshalb nicht direkt als enges Präfix verwenden.
Verbessert: Die Endung über eine umgekehrte Codespalte suchen
Bei häufigen Endungssuchen auf strukturierten Codes kann eine umgekehrte Darstellung helfen. Das Testskript legt dafür die berechnete Spalte BelegcodeRueckwaerts mit dem Ausdruck REVERSE(Belegcode) an und erstellt darauf einen Index.
Die Endung -4711 wird rückwärts zu 1174-. Daraus entsteht eine Präfixsuche:
SELECT COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE BelegcodeRueckwaerts LIKE '1174-%';
Für die Testdaten liefern beide Varianten dieselben Belege. Die Verbesserung benötigt allerdings eine zusätzliche indexierte Darstellung. Ein bloßes REVERSE(Belegcode) im Filter würde den dafür benötigten Index nicht ersetzen.
Das Beispiel beschränkt sich bewusst auf ASCII-Codes ohne abschließende Leerzeichen und verwendet für die Belegcodes eine binäre Kollation. Bei allgemeinen Texten musst du die Gleichwertigkeit unter der verwendeten Kollation und den vorhandenen Zeichenfolgen gesondert prüfen.
Der zusätzliche Index benötigt Speicher und muss bei Änderungen gepflegt werden. Für berechnete Spalten gelten außerdem Anforderungen an Determinismus, Datentypen und Sitzungseinstellungen. Diese beschreibt Microsoft unter Indizes auf berechneten Spalten.
Eine beliebige Teilzeichenfolge braucht eine andere Entscheidung
Eine umgekehrte Spalte löst keine allgemeine Suche wie LIKE '%4711%'. Auch nach dem Umdrehen bleibt die gesuchte Zeichenfolge möglicherweise mitten im Wert.
Je nach Datenmenge und Suchanforderung kommen ein akzeptierter Scan, eine zusätzliche Einschränkung über andere Spalten oder eine eigene Suchstruktur infrage. Für wortbasierte Inhalte kann die SQL-Server-Volltextsuche passen. Sie arbeitet jedoch mit sprachlichen Einheiten und ist kein allgemeiner Ersatz für beliebige Teilzeichenfolgen in Artikelnummern oder Codes.
Werden Suchmuster aus Benutzereingaben aufgebaut, beachte außerdem die Bedeutung von %, _ und [. Parameter schützen die Abfragestruktur, machen diese Zeichen aber nicht automatisch zu gewöhnlichen Suchzeichen. Die Musterregeln und ESCAPE erläutert die Dokumentation zu LIKE.
Reproduzierbares Testskript für SARGability
Das folgende Skript erzeugt 300.000 Zeilen in einer eigenen Demotabelle. Anschließend führt es die beschriebenen Abfragepaare aus. Die Datenverteilung ist deterministisch; es werden keine Zufallswerte verwendet.
Verwende dafür eine separate Testdatenbank auf SQL Server 2016 oder neuer. Das Skript legt eine Tabelle und mehrere Indizes an. Existiert bereits ein Objekt namens dbo.ITFundusSargDemo, bricht es ab, ohne dieses zu löschen.
Du benötigst Berechtigungen zum Anlegen der Objekte und Ausführen der Abfragen. Für die Planausgabe ist zusätzlich SHOWPLAN erforderlich.
Tatsächliche Ausführungspläne ausgeben
Das Skript verwendet SET STATISTICS XML ON. Damit werden die Abfragen ausgeführt und anschließend Pläne mit Laufzeitinformationen ausgegeben. In SQL Server Management Studio kannst du die ausgegebenen Plan-XMLs öffnen.
Lass für diesen Weg die zusätzliche SSMS-Option „Tatsächlichen Ausführungsplan einschließen“ ausgeschaltet. Alternativ kannst du die beiden STATISTICS XML-Anweisungen weglassen und die grafische Planerfassung mit Strg+M aktivieren.
Die IO- und Zeitangaben stehen im Reiter „Meldungen“. Die Bezeichnungen A1 bis D2 im Abfragetext und Ergebnis erleichtern die Zuordnung.
-- In einer separaten Testdatenbank ausfuehren.
-- Fuer die Planerfassung per STATISTICS XML die zusaetzliche
-- SSMS-Option "Tatsaechlichen Ausfuehrungsplan einschliessen"
-- ausgeschaltet lassen.
--
-- Den gesamten Block als einen Batch ausfuehren.
IF OBJECT_ID(N'dbo.ITFundusSargDemo') IS NOT NULL
BEGIN
;THROW 50000,
N'dbo.ITFundusSargDemo existiert bereits. Andere Testdatenbank verwenden oder die eigene Demotabelle bewusst entfernen.',
1;
END;
SET NOCOUNT ON;
-- Erforderliche Optionen fuer den Index auf der
-- berechneten Spalte.
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
CREATE TABLE dbo.ITFundusSargDemo
(
BestellungId int NOT NULL
CONSTRAINT PK_ITFundusSargDemo
PRIMARY KEY CLUSTERED,
Bestelldatum datetime2(0) NOT NULL,
Kundencode varchar(12)
COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
Belegcode varchar(20)
COLLATE Latin1_General_100_BIN2 NOT NULL,
BelegcodeRueckwaerts AS REVERSE(Belegcode) PERSISTED
);
-- Zahlen 1 bis 300000 ohne Zugriff auf vorhandene Fachtabellen.
;WITH Ziffern AS
(
SELECT d
FROM
(
VALUES (0), (1), (2), (3), (4),
(5), (6), (7), (8), (9)
) AS v(d)
),
Nummern AS
(
SELECT
1
+ z0.d
+ z1.d * 10
+ z2.d * 100
+ z3.d * 1000
+ z4.d * 10000
+ z5.d * 100000 AS n
FROM Ziffern AS z0
CROSS JOIN Ziffern AS z1
CROSS JOIN Ziffern AS z2
CROSS JOIN Ziffern AS z3
CROSS JOIN Ziffern AS z4
CROSS JOIN Ziffern AS z5
)
INSERT dbo.ITFundusSargDemo
(
BestellungId,
Bestelldatum,
Kundencode,
Belegcode
)
SELECT
n,
DATEADD
(
second,
n % 86400,
DATEADD
(
day,
n % 1096,
CONVERT(datetime2(0), '20240101', 112)
)
),
'K' + RIGHT
(
'000000' + CONVERT(varchar(6), n % 10000),
6
),
'B'
+ RIGHT('000000' + CONVERT(varchar(6), n), 6)
+ '-'
+ RIGHT('0000' + CONVERT(varchar(4), n % 10000), 4)
FROM Nummern
WHERE n <= 300000;
CREATE INDEX IX_ITFundusSargDemo_Bestelldatum
ON dbo.ITFundusSargDemo (Bestelldatum);
CREATE INDEX IX_ITFundusSargDemo_Kundencode
ON dbo.ITFundusSargDemo (Kundencode);
CREATE INDEX IX_ITFundusSargDemo_Belegcode
ON dbo.ITFundusSargDemo (Belegcode);
CREATE INDEX IX_ITFundusSargDemo_BelegcodeRueckwaerts
ON dbo.ITFundusSargDemo (BelegcodeRueckwaerts);
UPDATE STATISTICS dbo.ITFundusSargDemo WITH FULLSCAN;
-- Umgebung fuer einen spaeteren Vergleich dokumentieren.
SELECT
@@VERSION AS SQLServerVersion,
DB_NAME() AS Datenbank,
d.compatibility_level AS Kompatibilitaetsgrad,
d.collation_name AS Datenbankkollation
FROM sys.databases AS d
WHERE d.database_id = DB_ID();
SELECT COUNT_BIG(*) AS Gesamtzahl
FROM dbo.ITFundusSargDemo;
-- Messblock: Diesen Abschnitt fuer Wiederholungen erneut
-- ausfuehren, ohne Tabelle und Indizes neu anzulegen.
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SET STATISTICS XML ON;
-- A1: Datumsspalte in Text umwandeln.
SELECT
'A1: Datumsformatierung' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE CONVERT(char(8), Bestelldatum, 112) = '20260515';
-- A2: Direkter, halb offener Datumsbereich.
SELECT
'A2: Datumsbereich' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Bestelldatum >= '20260515'
AND Bestelldatum < '20260516';
-- A3: Sonderfall separat untersuchen.
-- CAST auf date kann einen Seek ermoeglichen.
SELECT
'A3: CAST auf date' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE CAST(Bestelldatum AS date) =
CONVERT(date, '20260515', 112);
-- B1: Funktion auf der Kundencode-Spalte.
SELECT
'B1: LEFT auf Spalte' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE LEFT(Kundencode, 5) = 'K0042';
-- B2: Dasselbe Praefix direkt suchen.
SELECT
'B2: LIKE mit Praefix' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Kundencode LIKE 'K0042%';
-- C1: nvarchar-Parameter gegen varchar-Spalte.
EXEC sys.sp_executesql
N'SELECT
''C1: nvarchar-Parameter'' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Kundencode = @Code;',
N'@Code nvarchar(12)',
@Code = N'K004200';
-- C2: Passend deklarierter varchar-Parameter.
EXEC sys.sp_executesql
N'SELECT
''C2: varchar-Parameter'' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Kundencode = @Code;',
N'@Code varchar(12)',
@Code = 'K004200';
-- D1: Endungssuche auf dem urspruenglichen Belegcode.
SELECT
'D1: LIKE mit Endung' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE Belegcode LIKE '%-4711';
-- D2: Dieselbe Endung als Praefix der umgekehrten Codes.
SELECT
'D2: Umgekehrter Code' AS Test,
COUNT_BIG(*) AS Treffer
FROM dbo.ITFundusSargDemo
WHERE BelegcodeRueckwaerts LIKE '1174-%';
SET STATISTICS XML OFF;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
Welche Ergebnisse müssen übereinstimmen?
Aus der Datenerzeugung ergeben sich die folgenden Trefferzahlen. Es handelt sich um erwartete fachliche Ergebnisse, nicht um behauptete Leistungsmessungen:
| Abfragen | Suchanforderung | Erwartete Treffer je Abfrage |
|---|---|---|
| A1, A2 und A3 | Bestellungen am 15. Mai 2026 | 273 |
| B1 und B2 | Kundencode beginnt mit K0042 | 3.000 |
| C1 und C2 | Kundencode ist K004200 | 30 |
| D1 und D2 | Belegcode endet mit -4711 | 30 |
Das Skript verwendet COUNT_BIG(*), damit keine großen Ergebnismengen zum Client übertragen werden. Damit untersucht es bewusst den Filterzugriff. Es bildet nicht automatisch die Kosten einer späteren Anwendung mit vielen Ausgabespalten, Sortierungen oder Joins ab.
Bei eigenen Umschreibungen solltest du zusätzlich die gefundenen Schlüsselwerte vergleichen, beispielsweise mit EXCEPT in beiden Richtungen. Gleiche Anzahlen allein beweisen bei beliebigen Daten noch keine gleichen Ergebnismengen.
Tatsächliche Ausführungspläne vergleichen
Ein tatsächlicher Ausführungsplan enthält neben dem kompilierten Plan Informationen aus der Ausführung. Ein geschätzter Plan führt die Abfrage dagegen nicht aus. Microsoft erläutert die grafische Erfassung unter Tatsächlichen Ausführungsplan anzeigen.
Öffne für jedes Abfragepaar den zugreifenden Indexoperator und dessen Eigenschaften. Je nach SSMS-Version unterscheiden sich einzelne Bezeichnungen etwas.
- Verwendeter Index: Welcher Index wird tatsächlich gelesen?
- Seek Predicates: Welche Bedingungen begrenzen den Suchbereich?
- Predicate: Welche zusätzliche Bedingung wird auf bereits gelesene Kandidaten angewendet?
- Actual Number of Rows Read: Wie viele Zeilen liest der Operator vor seiner zusätzlichen Filterung?
- Actual Number of Rows: Wie viele Zeilen gibt dieser Operator weiter?
- Ausführungszahl: Wie häufig wird der Operator aufgerufen?
- Schätzung und tatsächliche Zeilen: Gibt es große Abweichungen?
Bei den COUNT-Abfragen liefert das abschließende Aggregat jeweils nur eine Ergebniszeile. Für den Vergleich von gelesenen Zeilen und Treffern musst du deshalb den zugreifenden Operator vor dem Aggregat betrachten. Bei parallelen Plänen sind zudem die Werte der beteiligten Threads beziehungsweise die zusammengefassten Angaben zu beachten.
Diese Unterschiede sind im Test zu untersuchen
Die folgende Tabelle beschreibt typische Planformen und die dazugehörigen Prüfpunkte. Sie ersetzt keine Ausführung auf deinem System. SQL-Server-Version, Kompatibilitätsgrad und Optimiererentscheidungen können die konkrete Darstellung verändern.
| Vergleich | Typische problematische Planform | Typische verbesserte Planform | Entscheidender Prüfpunkt |
|---|---|---|---|
| A1 gegen A2 | Scan mit Prüfung der Datumsformatierung | Seek auf einen Tagesbereich | Gelesene Zeilen und logische Lesezugriffe |
| A3 gegen A2 | Auch A3 kann einen Seek verwenden | Ausdrücklich formulierter Tagesbereich | Suchgrenzen, Restbedingung und tatsächliche Lesemenge |
| B1 gegen B2 | Scan mit LEFT-Auswertung | Seek über ein Präfix | Ob das Präfix den Zugriff begrenzt |
| C1 gegen C2 | Scan mit Spaltenkonvertierung unter der gewählten SQL-Kollation | Gleichheits-Seek | Position von CONVERT_IMPLICIT und tatsächliche IO-Werte |
| D1 gegen D2 | Scan für die Endungssuche | Seek auf dem Index der umgekehrten Codes | Gleiche Treffer bei kleinerer gelesener Datenmenge |
Wenn die erwartete Planform nicht erscheint, erzwinge sie nicht sofort mit einem Hint. Prüfe zuerst Indexdefinition, Kollation, Sitzungseinstellungen, Statistiken und die tatsächlich verwendeten Parameter.
SET STATISTICS IO und TIME richtig auswerten
Logische Lesezugriffe zeigen die gelesenen Seiten
SET STATISTICS IO ON schreibt die Leseaktivität in den Meldungsbereich. Besonders hilfreich sind die logical reads: Sie zählen Seitenzugriffe im Datencache. Wiederholte Zugriffe auf dieselbe Seite zählen dabei erneut.
Sinken bei gleichem Ergebnis die logischen Lesezugriffe deutlich, ist das ein konkreter Hinweis auf weniger Arbeit beim Datenzugriff. Vergleiche dabei alle relevanten Tabellen und gegebenenfalls Arbeitstabellen der Abfrage.
physical reads = 0 bedeutet lediglich, dass für diese Zugriffe keine Seiten vom Datenträger nachgeladen werden mussten. Eine Abfrage kann trotzdem sehr viele Seiten aus dem Cache lesen und entsprechend Arbeit verursachen.
Auch Scan count ist keine einfache Bewertung als „guter Seek“ oder „schlechter Scan“. Die genaue Bedeutung der Ausgabefelder beschreibt Microsoft unter SET STATISTICS IO.
CPU-Zeit und verstrichene Zeit gemeinsam betrachten
SET STATISTICS TIME ON zeigt Angaben für Parsing, Kompilierung und Ausführung. Trenne die Kompilierungsangaben von den eigentlichen Ausführungszeiten.
Die CPU-Zeit beschreibt Prozessorarbeit, die verstrichene Zeit die Dauer der Ausführung. Wartezeiten und Parallelität können dazu führen, dass beide Werte deutlich voneinander abweichen. Bei paralleler Ausführung kann die aufsummierte CPU-Zeit auch größer als die verstrichene Zeit sein.
Sehr kurze Abfragen können mit null oder wenigen Millisekunden erscheinen. Daraus lässt sich kein belastbarer prozentualer Vorteil ableiten. Die Ausgabe ist in der Dokumentation zu SET STATISTICS TIME beschrieben.
So wird der Vergleich aussagekräftiger
- Führe den Messblock zunächst zum Aufwärmen aus.
- Wiederhole anschließend die Abfragepaare mehrfach mit unveränderten Daten.
- Wechsle auch die Reihenfolge der beiden Varianten, damit nicht stets dieselbe Abfrage zuerst läuft.
- Notiere Treffer, logische Lesezugriffe, CPU-Zeit und verstrichene Zeit je Variante.
- Bewerte wiederkehrende Unterschiede, nicht nur den besten Einzelwert.
- Prüfe die endgültige Anwendungsabfrage zusätzlich mit ihrer tatsächlichen Spaltenliste und repräsentativen Parametern.
Die Erfassung tatsächlicher Pläne verursacht selbst zusätzlichen Aufwand. Untersuche zunächst die Pläne und führe für ergänzende Zeitmessungen die Abfragen auch mit ausgeschalteter Planerfassung aus. Aktiviere dafür weiterhin STATISTICS IO und STATISTICS TIME.
Leere für diesen Vergleich nicht den globalen Plan- oder Datencache einer gemeinsam genutzten Instanz. Der Test benötigt solche Eingriffe nicht.
Warum ein Index Seek allein keinen Leistungsnachweis liefert
Ein Seek beschreibt die Art des Zugriffs. Er besagt nicht, dass nur wenige Zeilen gelesen werden. Ein Suchbereich kann groß sein, eine Restbedingung viele Kandidaten verwerfen oder ein Operator sehr oft ausgeführt werden.
Ein Seek kann viele Kandidaten nachträglich verwerfen
In den Eigenschaften eines Index Seek können sowohl Seek Predicates als auch ein zusätzliches Predicate stehen. Die Suchbedingungen grenzen zunächst den Indexbereich ein. Die zusätzliche Bedingung prüft anschließend die daraus gelesenen Kandidaten.
Gibt der Operator nur wenige Zeilen weiter, hat aber sehr viele gelesen, lohnt sich ein Blick auf diese Restbedingung. Sie kann direkt im Seek ausgewertet werden; ein eigener sichtbarer Filteroperator ist dafür nicht zwingend vorhanden.
Microsoft beschreibt diese Trennung in der Referenz der Ausführungsplanoperatoren.
Viele kleine Seeks können zusammen teuer sein
Ein Seek innerhalb eines Nested-Loops-Joins kann für zahlreiche äußere Zeilen wiederholt werden. Der einzelne Zugriff wirkt klein, seine gesamte Ausführungszahl verursacht aber möglicherweise viele Seitenzugriffe.
Betrachte deshalb neben den Zeilenangaben auch die Ausführungszahl. Achte darauf, ob eine angezeigte Eigenschaft Werte je Ausführung oder über alle Ausführungen zusammenfasst.
Key Lookups können den Vorteil aufzehren
Ein nicht abdeckender Index liefert möglicherweise nur die passenden Schlüssel. Weitere Ausgabespalten werden dann über Key Lookups aus dem Clustered Index geholt.
Für wenige Treffer kann das günstig sein. Bei vielen Treffern können die zusätzlichen Zugriffe teurer werden als ein Scan. Ein passend gewähltes INCLUDE kann helfen, vergrößert aber den Index und erhöht dessen Pflegekosten.
Die COUNT-Abfragen im Labortest halten diesen Aspekt bewusst klein. Sobald die echte Abfrage weitere Spalten benötigt, muss ihr vollständiger Plan erneut bewertet werden.
Ein Scan kann die richtige Entscheidung sein
Wenn ein Bericht fast alle Bestellungen benötigt, spart ein Seek möglicherweise kaum Datenzugriffe. Ein Scan über einen schmalen, abdeckenden Index kann dann effizient sein.
Auch die im grafischen Plan angezeigten Kostenprozente sind kein Laufzeitmesser. Sie beruhen weiterhin auf Kostenschätzungen, selbst wenn der Plan tatsächliche Laufzeitinformationen enthält.
Für die Bewertung gehören daher mehrere Informationen zusammen: korrektes Ergebnis, gelesene Zeilen, logische Lesezugriffe, CPU-Zeit, Dauer und der gesamte Ausführungsplan.
Indexfreundliche Suchbedingungen in bestehenden Anwendungen prüfen
Beginne mit einer Abfrage, deren Ressourcenverbrauch oder Häufigkeit eine Optimierung rechtfertigt. Anschließend lässt sich die Untersuchung in wenigen Schritten durchführen:
- Suchanforderung klären: Geht es um einen Zeitraum, einen exakten Wert, ein Präfix, eine Endung oder eine beliebige Teilzeichenfolge?
- Typen prüfen: Stimmen Spalten- und Parametertypen überein? Bei Zeichenfolgen sind außerdem Länge und Kollation relevant.
- Spaltenausdrücke untersuchen: Sind Funktionen oder Berechnungen im Filter fachlich notwendig oder lassen sie sich auf die Parameterseite verlagern?
- Indexschlüssel prüfen: Passt die Reihenfolge der Schlüsselspalten zur tatsächlichen Suche?
- Ergebnisse vergleichen: Liefert die Umschreibung dieselben Schlüsselwerte?
- Arbeit messen: Vergleiche tatsächliche Pläne, logische Lesezugriffe und Zeiten.
- Gesamtlast berücksichtigen: Prüfe zusätzliche Indizes auch hinsichtlich Speicherbedarf und Schreibaufwand.
Wenn du zunächst die relevanten Abfragen finden oder Veränderungen über längere Zeit beobachten möchtest, hilft die Anleitung Indexanalyse im SQL Server mit Query Store, Ausführungsplänen und DMVs.
Werbung / Affiliate-Links: Die mit einem Sternchen (*) gekennzeichneten Links sind Amazon-Partnerlinks. Wenn du darüber etwas kaufst, erhalte ich möglicherweise eine Provision. Für dich entstehen dadurch keine zusätzlichen Kosten. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen.
-
T-SQL Fundamentals (Developer Reference) *
Weiterführende Lektüre zu den Grundlagen von T-SQL.
→ T-SQL Fundamentals (Developer Reference) bei Amazon ansehen*
-
T-SQL Querying (Developer Reference) *
Zur Vertiefung von T-SQL-Abfragen.
-
T-SQL Window Functions: For data analysis and beyond (Developer Reference) *
Weiterführende Lektüre zu Window Functions in T-SQL.
→ T-SQL Window Functions: For data analysis and beyond (Developer Reference) bei Amazon ansehen*
Demotabelle nach dem Test entfernen
Wenn du die Vergleiche abgeschlossen hast, kannst du die selbst angelegte Demotabelle einschließlich ihrer Indizes in der verwendeten Testdatenbank entfernen:
DROP TABLE dbo.ITFundusSargDemo;
Weiterführende Informationen
- Microsoft Learn: Indexarchitektur und Indexdesign
- Microsoft Learn: Datentyppriorität
- Microsoft Learn: LIKE und Suchmuster
- Microsoft Learn: Indizes auf berechneten Spalten
- Microsoft Learn: Tatsächlichen Ausführungsplan anzeigen
- Microsoft Learn: SET STATISTICS XML
- Microsoft Learn: SET STATISTICS IO
- Microsoft Learn: SET STATISTICS TIME