Kompendium · Optimizer verstehen · Kapitel 1

Eine Abfrage, viele Pläne.

Dieselbe Prozedur läuft mal in Millisekunden und mal quälend lange? Oft liegt das nicht an der Hardware. Es liegt daran, welchen Ausführungsplan der Optimizer gewählt hat, und welcher gerade im Cache liegt. Probieren Sie es aus: mit echten Plänen aus unserem Testlab.

Stand 29.09.2026Reifegrad GrundrissKapitel 1 von 7 ↓
Experiment 1

Der Optimizer wählt je Wert einen anderen Weg.

In unserem Testlab liegen 1.510.967 Bestellungen mit 4.535.871 Positionen von 50.000 Kunden. Die Verteilung ist bewusst ungleich, wie im echten Leben: Manche Kunden haben eine einzige Bestellung, einer hat 500.000. Die Prozedur rechnet den Umsatz je Bestellung eines Kunden aus. Es gibt einen Index auf der Kundennummer.

dbo.KundenUmsatz @CustomerID
SELECT o.OrderID, o.OrderDate, SUM(l.Amount) AS Umsatz
FROM dbo.Orders o
JOIN dbo.OrderLines l ON l.OrderID = o.OrderID
WHERE o.CustomerID = @CustomerID
GROUP BY o.OrderID, o.OrderDate;
Für Interessierte: Tabellen, Indizes und Datenverteilung
Schema des Testlabs (Kompatibilitätsgrad 170)
CREATE TABLE dbo.Customers (
  CustomerID int           NOT NULL PRIMARY KEY,
  Name       nvarchar(100) NOT NULL,
  Region     char(2)       NOT NULL);

CREATE TABLE dbo.Orders (
  OrderID    int IDENTITY  NOT NULL PRIMARY KEY CLUSTERED,
  CustomerID int           NOT NULL,
  OrderDate  date          NOT NULL,
  Status     tinyint       NOT NULL,
  Total      decimal(12,2) NOT NULL,
  Filler     char(200)     NOT NULL DEFAULT 'x');

CREATE TABLE dbo.OrderLines (
  OrderID    int           NOT NULL,
  [LineNo]   tinyint       NOT NULL,
  ProductID  int           NOT NULL,
  Qty        int           NOT NULL,
  Amount     decimal(12,2) NOT NULL,
  PRIMARY KEY CLUSTERED (OrderID, [LineNo]));

CREATE INDEX IX_Orders_CustomerID ON dbo.Orders (CustomerID);
  • 50.000 Kunden
  • 1.510.967 Bestellungen, davon sieben Stufenkunden mit 1, 10, 100, 1.000, 10.000, 100.000, 500.000 Bestellungen; alle anderen haben 1 bis etwa 40
  • 4.535.871 Positionen, 1 bis 5 je Bestellung
  • Statistiken mit FULLSCAN aktualisiert; der Index auf CustomerID deckt die Abfrage bewusst nicht ab, deshalb der Key Lookup

Schieben Sie den Regler. Jede Stufe ist ein eigener Aufruf der Prozedur, jeweils frisch kompiliert.

Tatsächlicher Ausführungsplan…

Ein Plan lässt sich in beide Richtungen lesen. Von rechts nach links folgen Sie den Daten: Rechts werden sie gelesen, links steht das Ergebnis. Von links nach rechts folgen Sie der Ausführung: Jeder Operator fordert seine Zeilen beim rechten Nachbarn an. Die Pfeilstärke zeigt, wie viele Zeilen fließen.

Geschätzte Zeilen
–
Logische Reads
–
CPU
–
Dauer
–
Experiment 2

Und dann bleibt ein Plan im Cache.

SQL Server kompiliert eine Prozedur beim ersten Aufruf mit dem Wert, der gerade übergeben wird, und verwendet den Plan danach für alle weiteren Aufrufe. Welcher Kunde zuerst kommt, entscheidet also für alle anderen. Das heißt Parameter Sniffing.

Einordnung

Warum der Optimizer so arbeitet

Der Optimizer von SQL Server ist kostenbasiert. Er schätzt, wie viele Zeilen jeder Schritt liefert, und wählt aus vielen möglichen Plänen den mit den geringsten geschätzten Kosten. Grundlage der Schätzung sind Statistiken über die Verteilung der Daten und, bei Parametern, der Wert beim Kompilieren.

Für wenige Zeilen ist der gezielte Zugriff über einen Index unschlagbar. Für sehr viele Zeilen ist es günstiger, die Tabelle einmal ganz zu lesen und per Hash zu verknüpfen. Keiner der beiden Pläne ist falsch. Falsch wird es erst, wenn ein Plan für Daten verwendet wird, für die er nicht gebaut wurde.

Eine Beobachtung am Rande: Beim Kunden mit 10.000 Bestellungen wählte der Optimizer einen parallelen Plan. Er war in unserer Messung nicht schneller als der wiederverwendete Plan des kleinsten Kunden (288 ms gegenüber 242 ms), brauchte aber ein Vielfaches an CPU (1,1 s gegenüber 242 ms). Der Optimizer vergleicht geschätzte Kosten, keine gemessenen Laufzeiten. Auch der „passende“ Plan ist nur der mit den niedrigsten Schätzkosten.

Was dagegen hilft, und was es kostet

AnsatzWirkungPreis
OPTION (RECOMPILE)passender Plan bei jedem AufrufKompilierung bei jedem Aufruf; bei häufig ausgeführten Abfragen kostet das erheblich CPU, dafür muss Budget da sein
OPTIMIZE FORein bewusst gewählter Plan für alleKompromiss; passt für einen Teil der Werte schlechter
Dynamisches SQL je Abfrageformeigene, wiederverwendbare Pläne je FormPflegeaufwand; mit unserem Open-Source-Projekt T-Lift deutlich geringer
Parameter Sensitive Plan Optimizationab SQL Server 2022 bis zu drei Planvarianten je Abfragegreift nur unter bestimmten Bedingungen, siehe unten
Query Store: Plan erzwingen oder Hint setzengezielte Korrektur ohne Codeänderungmuss beobachtet werden, wenn sich Daten oder Version ändern

Und die Parameter Sensitive Plan Optimization?

Seit SQL Server 2022 (Kompatibilitätsgrad 160) kann SQL Server für eine Abfrage mit ungleich verteilten Parameterwerten bis zu drei Planvarianten vorhalten. Das zielt genau auf das Problem aus Experiment 2.

Mit eingeschalteter PSP-Optimierung legte unser Testlab für die Prozedur einen Verteilerplan und 3 Varianten an:

  • Variante 1 für Kunden mit einer Bestellung: Nested Loops, seriell
  • Variante 2 für Kunden mit 10 bis 10.000 Bestellungen: Nested Loops, seriell
  • Variante 3 für Kunden mit 100.000 bis 500.000 Bestellungen: Hash Match, parallel

Das entschärft Experiment 2 deutlich, aber nicht vollständig: Kunden mit 10 und mit 10.000 Bestellungen teilen sich eine Variante. Welche Variante greift, entscheidet die Schätzung, nicht die tatsächliche Zeilenzahl. Die Kunden mit 10, 100 und 1.000 Bestellungen schätzt SQL Server hier alle auf 370 Zeilen, weil sie im selben Schritt des Statistik-Histogramms liegen. Genau darum geht es in Kapitel 2.

Wie wir solche Fälle finden

Der Query Store von SQL Server hält fest, welche Pläne eine Abfrage im Lauf der Zeit hatte und wie teuer jeder war. PSG QX wertet genau diese Historie aus: welche Abfrage mehrere Pläne hatte, welcher teurer war und seit wann. Ist der Query Store noch nicht aktiv, schalten wir ihn zu Beginn ein und lassen ihn sich füllen.

Messwerte

Alle Zahlen des Experiments

Logische Reads und Dauer je Kunde, mit passendem Plan und mit einem wiederverwendeten Plan aus dem Cache. Die gestrichelte Linie markiert, wo der passende Plan wechselt.

Bestellungen des KundenPassender PlanPassendPlan des kleinsten KundenPlan des größten Kunden
1Plan A
Nested Loops · Index Seek · Key Lookup · Clustered Index Seek
9
< 1 ms
9
< 1 ms
54.094
1,8 s
10Plan A
Nested Loops · Index Seek · Key Lookup · Clustered Index Seek
53
< 1 ms
63
< 1 ms
47.424
915 ms
100Plan A
Nested Loops · Index Seek · Key Lookup · Clustered Index Seek
574
2 ms
604
2 ms
58.224
2,9 s
1.000Plan A
Nested Loops · Index Seek · Key Lookup · Clustered Index Seek
5.879
43 ms
6.021
41 ms
51.677
6,6 s
10.000Plan B
Hash Match · Index Seek · Key Lookup · Clustered Index Scan · parallel
46.475
288 ms
60.165
242 ms
46.570
794 ms
100.000Plan C
Hash Match · Clustered Index Scan · parallel
61.686
626 ms
601.649
1,4 s
60.782
2,7 s
500.000Plan D
Merge Join · Clustered Index Scan
60.782
4,0 s
3.008.513
6,0 s
60.782
3,7 s
Kompendium

Optimizer verstehen: die Kapitel

Dieses Thema wächst. Kapitel, die noch in Arbeit sind, stehen hier bereits, damit Sie sehen, wohin es geht.

  1. 01
    Eine Abfrage, viele PläneDieses Kapitel
  2. 02
    Wie der Optimizer schätztKardinalität, Statistiken, Histogramme · in Arbeit
  3. 03
    Parameter Sniffing im DetailErkennen und die Gegenmittel im Vergleich · in Arbeit
  4. 04
    Plan Cache und WiederverwendungWarum ein Neustart scheinbar hilft · in Arbeit
  5. 05
    Kompatibilitätsgrad und SchätzermodellWas sich beim Upgrade ändert, siehe SQL-Server-Versionen · in Arbeit
  6. 06
    Neuere Optimizer-FunktionenIntelligent Query Processing ab SQL Server 2022 · in Arbeit
  7. 07
    Pläne sichtbar machenQuery Store und PSG QX · in Arbeit
Pflege

Methode und Änderungsprotokoll

Gemessen am 29.09.2026 auf Microsoft SQL Server 2025 (RTM-CU7) (KB5096981) - 17.0.4065.4 (X64) in einem Container mit 8 CPUs und 6.144 MB Speicher, Kompatibilitätsgrad 170, MAXDOP 0. Warmer Cache, Dauer und CPU als Median aus zehn Läufen; bei gleichem Plan streuen die Zeiten bis Faktor 2, Werte unter einer Millisekunde sind nur Größenordnungen. Die Dauer enthält das Senden der Ergebniszeilen an den Client. Für die Experimente 1 und 2 war die Parameter Sensitive Plan Optimization ausgeschaltet, damit das klassische Verhalten sichtbar wird.

Ihre Zahlen werden anders sein: andere Hardware, andere Daten. Die Form des Effekts bleibt. Die Skripte zum Nachstellen geben wir auf Anfrage gern weiter.

Änderungsprotokoll

  1. Kapitel 1 angelegt: zwei Experimente mit echten Plänen aus SQL Server 2025 CU7, Messwerte, Einordnung der Gegenmittel und Ergebnis zur Parameter Sensitive Plan Optimization.