Wissen · Lab-Beitrag

Gleiche Abfrage, anderes Tempo

Eine Abfrage ist im Test schnell und in Produktion langsam. Der SQL-Text ist gleich, vielleicht sogar der Plan. Brent Ozars neues Werkzeug sp_BlitzPlanCompare legt beide Seiten nebeneinander und listet die Unterschiede. Wir haben es im Lab auf SQL Server 2022 und 2025 durchgetestet: Was findet es, was bedeuten die Befunde, und wie wird aus einem Unterschied eine Ursache?

22.10.2026 · ca. 15 Minuten · Lab vom 21.10.2026 · SQL Server 2022 und 2025, Funktionsproben 2017 und 2019

Lab SQL Server 2022 · 202512 Bilder zum MitmachenMitraten Quiz ↓

Kurz gesagt

  • Das Werkzeug spart Sammelarbeit. sp_BlitzPlanCompare bringt Parameter, Indexe, Statistiken, Einstellungen, Pläne und Wartezustände zweier Server in eine priorisierte Liste.
  • Es hat im Lab die richtigen Spuren gefunden: unterschiedliche Compile-Werte, geänderte Statistikköpfe, den ersten Operator mit abweichender Zeilenmenge (500 statt 3.000) und eine aktuelle Sperre.
  • Eine Spur ist noch keine Ursache. Ein unbenutzter Index bekam Priorität 1, ebenso ein erzwungener Plan einer ganz anderen Prozedur.
  • Die Ursache belegt erst die Gegenprobe. Beim selben Laufzeitwert las der Plan, der für einen seltenen Kunden kompiliert war, rund 270.000 Seiten, der andere rund 2.800.

MerksatzEin Unterschied ist eine Spur, keine Ursache. Zur Ursache wird er erst durch eine Gegenprobe.

Gleiche Abfrage, anderes Tempo

Der Anruf kommt meist so: „Im Test läuft das in einer Sekunde, in Produktion dauert es eine Minute.“ Der SQL-Text ist derselbe. Wo fängt man an? Bei den Indexen, bei den Statistiken, bei den Parametern, bei den Servereinstellungen oder bei einer Sperre, die gerade jemand hält?

Wer das von Hand untersucht, sammelt Informationen an vielen Stellen: Plan Cache, Indexdefinitionen, Statistikköpfe, Datenbankoptionen, Wartezustände. Und dann legt man alles nebeneinander. Genau diese Sammelarbeit nimmt sp_BlitzPlanCompare ab. Brent Ozar hat das Werkzeug im Oktober 2026 als Teil des kostenlosen First Responder Kit vorgestellt (Brents Vorstellung). Danke dafür an Brent und alle, die am First Responder Kit mitarbeiten.

Wir wollten mehr wissen als „es kommt eine Liste“. Deshalb haben wir in einem eigenen Lab gezielt Unterschiede erzeugt und geprüft, welche das Werkzeug findet und wie man sie liest. Dabei stoßen wir auf drei verbreitete Fehlannahmen:

  1. „Was ganz oben steht, ist die Ursache.“ Die Priorität sortiert Spuren. Sie prüft nicht, ob ein Unterschied die langsame Ausführung erklärt.

    aufgelöst in: Priorität ist keine Diagnose ↓
  2. „Gleiche Abfrage, gleicher Plan, gleiche Arbeit.“ Derselbe Plan kann je nach Parameter sehr unterschiedlich viel lesen.

    aufgelöst in: Fall 1 ↓
  3. „Langsamer heißt schlechterer Plan.“ Ein guter Plan kann trotzdem warten, zum Beispiel auf eine Sperre.

    aufgelöst in: Fall 4 ↓

Jeder der folgenden Fälle löst eine dieser Annahmen auf.

Das Modell: vier Ebenen eines fairen Vergleichs

Bevor wir Befunde lesen, brauchen wir eine Ordnung. Ein Ausführungsplan ist der Arbeitsweg, den SQL Server für eine Abfrage wählt: zum Beispiel Zeilen über einen Index suchen, Tabellen verknüpfen, summieren. Der Optimizer wählt diesen Weg beim Kompilieren, mit den Informationen, die er in diesem Moment hat. Später wird der Plan ausgeführt, unter den Bedingungen, die dann gerade herrschen. Das sind zwei verschiedene Zeitpunkte.

Daraus ergeben sich vier Ebenen, auf denen sich eine schnelle und eine langsame Seite unterscheiden können:

  1. 1Kontext Vergleichen wir wirklich dasselbe Statement, in derselben Datenbank, mit denselben Sitzungseinstellungen?
  2. 2Kompilierung Hatte der Optimizer auf beiden Seiten dieselben Zutaten, also Parameterwerte, Indexe, Statistiken, Schätzmodell und Optionen?
  3. 3Plan Welchen Arbeitsweg hat er jeweils gewählt, und wie viele Zeilen sind tatsächlich durch die einzelnen Schritte geflossen?
  4. 4Laufbedingungen Unter welchen Umständen lief der Plan: Sperren, Speicherzuteilung, CPU, I/O?

Die Ebenen bauen aufeinander auf: Die Kompilierung bestimmt den Plan, der Plan bestimmt die Arbeit. Die Laufbedingungen wirken von der Seite auf die Dauer, ohne den Plan zu ändern. sp_BlitzPlanCompare sammelt Informationen aus allen vier Ebenen. Unsere Aufgabe bleibt, jeden Befund seiner Ebene zuzuordnen und zu fragen, ob er die beobachtete Langsamkeit erklären kann. Microsofts Leitfaden für den Vergleich zweier Server folgt derselben Logik.

Bild 1Schema

Vier Ebenen, zwei Seiten

Jede Ebene wird waagerecht verglichen. Tippen Sie auf eine Ebene für Beispiele und die passenden Fälle.

schnellz. B. Test

langsamz. B. Produktion

1KontextStatement, Datenbank, Sitzungseinstellungen
1KontextStatement, Datenbank, Sitzungseinstellungen
2KompilierungParameterwerte, Indexe, Statistiken, Schätzmodell, Optionen
2KompilierungParameterwerte, Indexe, Statistiken, Schätzmodell, Optionen
3PlanArbeitsweg und tatsächliche Zeilen je Schritt
3PlanArbeitsweg und tatsächliche Zeilen je Schritt

ArbeitDauer

ArbeitDauer

4LaufbedingungenSperren, Speicher, CPU, I/O
4LaufbedingungenSperren, Speicher, CPU, I/O

1Kontext Dasselbe Statement?

Beispiele: derselbe SQL-Text, dieselbe Datenbank, dieselben Sitzungseinstellungen wie ARITHABORT.

Im Artikel: Andere Datenbanknamen · Zwei Cachekopien

2Kompilierung Dieselben Zutaten?

Beispiele: Parameterwerte beim Kompilieren, Indexe, Statistiken, Schätzmodell (Cardinality Estimator), Datenbankoptionen.

Im Artikel: Fall 1 · Parameter · Fall 2 · Statistik · Unbenutzter Index

3Plan Derselbe Weg, dieselben Mengen?

Beispiele: Index Seek mit Key Lookup oder Scan, geschätzte und tatsächliche Zeilen je Schritt.

Im Artikel: Fall 3 · Zeilenmengen · Lookups oder Scan

4Laufbedingungen Dieselben Umstände?

Beispiele: Sperren, Speicherzuteilung (Memory Grant), CPU, I/O.

Im Artikel: Fall 4 · Blocking · Drei Uhren

Schema. Die Kompilierung bestimmt den Plan, der Plan die Arbeit; die Laufbedingungen wirken von der Seite auf die Dauer. Ein Vergleich ist erst fair, wenn klar ist, auf welcher Ebene sich zwei Seiten unterscheiden.

Der erste Lauf: Snapshot mitnehmen, drüben vergleichen

Die Prozedur liegt im Repository des First Responder Kit unter OptionalScripts. Für den einfachsten Weg wird sie auf beiden Instanzen installiert. Auf der ersten Seite nennen wir die Abfrage, um die es geht, hier eine Stored Procedure:

Seite 1 · Snapshot erzeugenT-SQL
-- Seite 1 (z. B. Produktion): Snapshot erzeugen
-- Vor dem Einsatz auf Produktion: Code lesen und mit dem Betrieb abstimmen (Hinweis unten)
EXEC master.dbo.sp_BlitzPlanCompare
    @StoredProcName = N'dbo.usp_Orders',
    @DatabaseName   = N'PlanCompareLab';

Die Ergebnisspalte CallStack enthält einen fertigen Aufruf für die andere Seite. Darin steckt ein XML-Snapshot mit Plan und Metadaten. Diesen Aufruf führen wir auf der zweiten Instanz aus. Sie vergleicht den mitgebrachten Snapshot („Remote“) mit ihrem eigenen Stand („Local“) und liefert zwei Ergebnisse: die priorisierte Liste der Unterschiede und die beiden verglichenen Pläne. Eine Verbindung zwischen den Servern ist dafür nicht nötig. Wer bereits einen Linked Server mit RPC OUT hat, kann beide Seiten auch in einem Aufruf vergleichen; beide Wege haben im Lab funktioniert.

Ein praktischer Stolperstein: Der Aufruf ist ein langer Text. SSMS kürzt lange Textzellen in der Ergebnisansicht. Ein abgeschnittener Snapshot ist ein Übertragungsfehler, kein Befund. Vor dem Kopieren deshalb die Grenze für Textergebnisse prüfen und dem in der Hilfe des Werkzeugs beschriebenen Weg folgen.

Bild 2Schema1Kontext

Der Snapshot reist

langsam Seite 1 · z. B. Produktion

Schritt 1 sp_BlitzPlanCompare für dbo.usp_Orders aufrufen

Spalte CallStack: fertiger Aufruf mit Snapshot

Wird über den Linked Server direkt gelesen

In SSMS abgeschnitten? Dann ist das ein Übertragungsfehler, kein Befund.

schnell Seite 2 · z. B. Test

Schritt 2 Aufruf aus CallStack hier ausführenEin Aufruf vergleicht beide Seiten

Localeigener StandRemotemitgebrachter Snapshotüber den Linked Server
Ergebnis 1Unterschiede, priorisiertErgebnis 2die beiden Pläne
Schema. Ohne Verbindung zwischen den Servern: Der Snapshot reist als Text im Aufruf mit. Variante mit Linked Server und RPC OUT: ein Aufruf auf Seite 2 holt den Stand von Seite 1 direkt. Beide Wege haben im Lab funktioniert. „Local“ ist die Instanz, auf der der Vergleich läuft; „Remote“ die andere Seite.

Fall 1 · 2KompilierungDer erste Parameter prägt den Plan

Aufbau Eine Tabelle mit 100.000 Aufträgen. 90.000 gehören zu einem großen Kunden, ein seltener Kunde hat zehn. Ein Index auf der Kundennummer hilft beim Finden; den Betrag, der summiert wird, muss SQL Server aber für jede gefundene Zeile in der Tabelle nachschlagen (Key Lookup).

Bild 3Schema3Plan

Zehn Lookups oder neunzigtausend

Seek + Key Lookup90.000einzelne Nachschlagevorgänge

Scan1 ×jede Seite der Tabelle einmal, egal wie viele Treffer

Arbeit wächst mit den Treffern.

Schema, nicht maßstäblich. Der Index enthält die Kundennummer, aber nicht den Betrag; jeder Treffer braucht deshalb einen eigenen Nachschlagevorgang in der Tabelle. Bei vielen Treffern steht eine gezeichnete Linie für viele Nachschlagevorgänge.

Für zehn Treffer ist das ideal: zehn Mal suchen, zehn Mal nachschlagen, fertig. Für 90.000 Treffer heißt derselbe Weg: 90.000 einzelne Nachschlagevorgänge. Dann ist es günstiger, die Tabelle einmal ganz zu lesen. „Index Seek“ ist also kein Qualitätsurteil; es kommt auf die Menge an, die danach bearbeitet wird.

SQL Server kompiliert eine parametrisierte Prozedur mit dem Wert, der beim ersten Aufruf ankommt, und verwendet den Plan danach wieder. Das nennt man Parameter Sniffing, und es ist meistens nützlich: Der Optimizer plant für einen konkreten Bedarf statt für einen Durchschnitt. Heikel wird es, wenn ein Plan für zehn Zeilen später für 90.000 wiederverwendet wird (Microsoft zur Parameterempfindlichkeit).

Erwartung Wir kompilieren die Prozedur einmal für den seltenen und einmal für den großen Kunden und führen danach jeweils beide Werte aus. Vier Kombinationen. Welche liest am meisten?

Bild 4Lab-Messung2Kompilierung

Vorhersage: Welche Kombination liest am meisten?

Ihr Tipp: Wählen Sie die Zelle, die am meisten liest.

kompiliert fürseltenen Kunden@CustomerID = 42 · Lookup-Plan

kompiliert fürgroßen Kunden@CustomerID = 1 · Scan-Plan

ausgeführt fürseltenen Kunden10 Treffer

32logische Reads

10 Lookups

2.790logische Reads

ein Scan

ausgeführt fürgroßen Kunden90.000 Treffer

270.158logische Reads

90.000 Lookups

2.790logische Reads

ein Scan
1 Kästchen = 1.000 logische Reads (Seitenzugriffe), Teilkästchen anteilig. Lab-Messung: 100.000 Zeilen, 90.000 bzw. 10 Treffer, Median aus 5 warmen Läufen, Compatibility Level 140. SQL Server 2022 und 2025 lieferten dieselben Zahlen.

Beobachtung Der große Kunde mit einem Plan, der für den seltenen kompiliert wurde, braucht rund 270.000 logische Seitenzugriffe. Mit dem Plan für den großen Kunden sind es rund 2.800, fast hundertmal weniger, bei identischem Ergebnis. Umgekehrt ist für den seltenen Kunden gerade der Lookup-Plan günstig: 32 statt 2.790 Zugriffe. SQL Server 2022 und 2025 lieferten exakt dieselben Zahlen.

Kompiliert fürAusgeführt fürLogische Reads (Median, 5 warme Läufe)
seltenen Kundenseltenen Kunden32
seltenen Kundengroßen Kunden270.158
großen Kundenseltenen Kunden2.790
großen Kundengroßen Kunden2.790

Logische Reads zählen Seitenzugriffe der Engine, auch auf Seiten, die schon im Speicher liegen. Sie sind keine Plattenzugriffe. Für den Vergleich derselben Abfrage auf demselben Datenbestand sind sie aber ein sehr stabiles Maß für Arbeit.

Was das Werkzeug zeigte sp_BlitzPlanCompare meldete die unterschiedlichen Compile-Werte (@CustomerID = 1 gegenüber 42) und schrieb einen Reproducer mit beiden Aufrufen dazu. Das ist genau die richtige Spur.

Folgerung Die Ursache belegt erst die Kreuzprobe: gleicher Laufzeitwert, nur der Compile-Wert geändert, Arbeit gemessen. Was nicht folgt: ein Rezept. OPTION (RECOMPILE), ein abdeckender Index oder ein anderer Weg muss sich an der echten Arbeitslast, an der Schreiblast und am Betriebsaufwand messen lassen.

Zwei Details für die Praxis. Der ausgegebene Reproducer ist eine Vorlage: Er stellt nicht den ursprünglichen Kompilierzustand her, ein erneuter Aufruf kann einfach den vorhandenen Plan wiederverwenden. Auf SQL Server 2025 schrieb er die Zahlenwerte außerdem als (1) und (42); so scheiterte der Aufruf mit Fehler 102, ohne Klammern lief er. Und: Wir haben bewusst Compatibility Level 140 genutzt. Ab SQL Server 2022 kann Parameter Sensitive Plan Optimization für unterschiedliche Wertebereiche absichtlich mehrere Pläne führen (Microsoft zu PSP); mehrere Pläne sind dort kein Fehler.

Fall 2 · 2KompilierungAktuell heißt nicht gleich verteilt

Der Optimizer kennt die Treffermenge nicht im Voraus, er schätzt sie mit Statistiken. Ein Statistikobjekt hat einen Kopf (wann aktualisiert, wie viele Zeilen, welcher Stichprobenumfang, wie viele Änderungen seitdem) und ein Histogramm, das die Verteilung der Werte mit höchstens 200 Stufen beschreibt (Microsoft zu Statistikkopf und Histogramm).

Erwartung Wenn wir auf einer Seite Daten löschen und die Statistik neu erstellen, sollte das Werkzeug das bemerken.

Änderung Wir haben auf einer Seite 5.000 Zeilen entfernt und danach mit FULLSCAN aktualisiert.

Beobachtung sp_BlitzPlanCompare zeigte die neue Zeilenzahl (95.000 statt 100.000) und die geänderten Statistikköpfe.

Folgerung und Grenze Das Werkzeug vergleicht die Köpfe, nicht die Histogramme. Das ist eine bekannte Grenze dieser frühen Fassung: Brent nennt sie im Kopf des Skripts selbst, der Histogrammvergleich ist für eine spätere Version zurückgestellt. Zwei Statistiken können gleich frisch sein, gleich viele Zeilen beschreiben und trotzdem eine völlig andere Verteilung zeigen. Und zwei unterschiedliche Zeitstempel sind für sich noch kein Problem. Die eigentliche Frage lautet: Passt die Schätzung für das betroffene Prädikat zur tatsächlichen Treffermenge? Der Kopfvergleich führt zu dieser Frage; beantworten muss man sie im Plan (geschätzte gegenüber tatsächlichen Zeilen) oder im Histogramm.

Bild 5Illustration2Kompilierung

Illustration: gleiche Menge, andere Verteilung

schnell

Zeilen
100.000
Stichprobe
100.000
Aktualisiert
gleicher Zeitpunkt

gleichmäßig verteilt

langsam

Zeilen
100.000
Stichprobe
100.000
Aktualisiert
gleicher Zeitpunkt

stark schief: ein Kunde mit 90.000 Zeilengleichmäßig verteilt

Lab-Messung Was das Werkzeug tatsächlich gemeldet hat

  1. 100.000 → 95.000 Zeilenauf einer Seite 5.000 entfernt
  2. FULLSCANStatistik neu erstellt
  3. Kopf geändertZeilenzahl und Zeitstempel; sp_BlitzPlanCompare meldet es (Priorität 15)
Oben Illustration mit Modellzahlen: zwei gleiche Statistikköpfe, darunter zwei mögliche Verteilungen derselben 100.000 Zeilen in 12 Stufen. Der Kopfvergleich sieht keinen Unterschied. Unten getrennt davon der Lab-Befund: Ändert sich der Kopf, meldet das Werkzeug es.

Auf derselben Ebene liegt das Schätzmodell. Compatibility Level und Cardinality Estimator sind zwei verschiedene Dinge: Mit LEGACY_CARDINALITY_ESTIMATION = ON lief eine Datenbank auf Level 150 im Plan mit CE-Version 70, die Vergleichsseite mit 140. Das Werkzeug zeigte beide Ebenen. Aus der Engineversion allein lässt sich das aktive Schätzmodell also nicht ablesen (Microsoft zum Cardinality Estimator).

Fall 3 · 3PlanWo die Zeilenmengen auseinanderlaufen

Zwei gleich geformte Pläne können sehr unterschiedliche Mengen bearbeiten. Für die Diagnose ist interessant, an welcher Stelle sich der Zeilenfluss zuerst unterscheidet, denn die Schritte darüber arbeiten mit dieser Menge weiter.

Änderung Beide Seiten nutzen denselben parametrisierten Plan; die Bereichsgrenze ist einmal 3.000 und einmal 500.

Beobachtung sp_BlitzPlanCompare markierte den Clustered Index Seek (Knoten 3) mit 500 gegenüber 3.000 tatsächlichen Zeilen als erste auffällige Stelle, auf SQL Server 2022 und 2025 gleich. Im Bild sieht man: Auch der zweite Seek (Knoten 4) liefert 500 statt 3.000 Zeilen, denn derselbe Parameter begrenzt beide Zugriffe, und der Nested Loops darüber übernimmt die Menge. Das Werkzeug nennt die erste Abweichung in seiner Durchlaufreihenfolge, nicht jede.

Bild 6Lab-Pläne3Plan

Zeilenfluss in zwei gleichen Plänen

Orange umrandet: Knoten 3, die erste vom Werkzeug markierte Mengenabweichung (500 gegenüber 3.000).

Lab-Pläne, SQL Server 2022, vereinfacht auf Operator, Knoten und Zeilen; alle fünf Operatoren sind dargestellt. Knoten 3 und 4 sind zwei Zugriffe auf dieselbe Tabelle, beide durch denselben Parameter begrenzt; markiert ist der, den das Werkzeug zuerst meldet. Strichstärke im Verhältnis der tatsächlichen Zeilen; die wandernden Punkte illustrieren den Fluss von unten nach oben. „Schätzung“: geschätzte Zeilen des gemeinsamen Plans.

Folgerung Das ist eine echte Abkürzung: Statt zwei Pläne Knoten für Knoten zu vergleichen, schaut man auf die markierte Stelle. Sie sagt, wo die Mengen auseinanderlaufen, nicht warum. Anderer Parameter, anderer Datenbestand, anderer Filter? Das ist die nächste Frage. Und die erste Abweichung ist nicht automatisch der teuerste Schritt.

Damit der Vergleich trägt, müssen die Pläne zueinander passen. Bei gleicher Form vergleicht das Werkzeug die einander entsprechenden Knoten; bei unterschiedlicher Form nur den obersten. Echte Zeilenzahlen nutzt es nur, wenn beide Seiten Laufzeitwerte haben, sonst Schätzungen. Bei Prozeduren mit mehreren Statements lohnt deshalb ein Blick darauf, welche Pläne tatsächlich verglichen wurden.

Fall 4 · 4LaufbedingungenGleicher Plan, längere Wartezeit

Erwartung Wenn eine Abfrage plötzlich Sekunden statt Millisekunden braucht, liegt der Gedanke an einen schlechteren Plan nahe.

Änderung Eine schreibende Transaktion hält eine Sperre auf genau der Zeile, die unsere Abfrage lesen will.

Beobachtung Der Leser wartet, der Plan bleibt derselbe. sp_BlitzPlanCompare zeigte eine blockierte Sitzung und den aktuellen Wartetyp LCK_M_S. Die Meldung zu LiveState · BlockedSessions sagt dabei selbst: The slow server may be losing time to blocking, not the plan. Nach dem Zurückrollen des Schreibers war der Leser sofort fertig.

Bild 7SchemaLab-Wert ≈ 2,4 s4Laufbedingungen

Ein Plan, zwei Läufe

Plan · in beiden Läufen derselbeClustered Index ScanStream Aggregate

Schema: Zeitachse nicht maßstäblich, nur die Wartezeit von rund 2,4 s stammt aus einem gezielt blockierten Lab-Lauf. Der Plan ist in beiden Läufen derselbe; die Dauer entsteht beim Warten auf die Sperre.

Folgerung Hier hilft kein neuer Index. Die richtige nächste Frage gilt dem Blocker: Wer hält die Sperre, wie lange, und warum ist die Transaktion so lang? (Mehr dazu im Ratgeber-Weg Blocking und Timeouts.)

Ähnlich ist es bei anderen Ressourcen. Ein Memory Grant ist eine Speicherreservierung für Sortieren oder Hashing. Ist zu wenig Speicher frei, wartet die Abfrage oder lagert nach tempdb aus; ist der Grant viel zu groß, fehlt der Speicher anderen (Microsoft zu Memory Grants). Auch CPU-Zeit und Dauer gehören getrennt betrachtet: Bei parallelen Plänen kann die summierte CPU-Zeit größer sein als die Uhrzeit.

Priorität ist keine Diagnose

Die Ergebnisliste sortiert Unterschiede nach einer im Werkzeug hinterlegten Wichtigkeit. Das ist ein guter Einstieg, solange man jede Zeile mit zwei Fragen liest: Was sagt die Zeile? und Was beweist sie für meine Abfrage? Vier Gegenproben aus unserem Lab zeigen, warum die zweite Frage nötig ist.

Bild 8Lab-Gegenproben

Befundkarten zum Umdrehen

Vorderseite: die Ergebniszeile im Original, wie das Werkzeug sie ausgibt (englisch). Umdrehen:Rückseite: was sie beweist und wie die Gegenprobe ausging.

  • 2KompilierungAusgabe des Werkzeugs (Original, englisch)

    Priority 1 Category Index Setting Present

    Object PlanCompareLab.dbo.Orders.IX_Orders_Amount_Unused

    LocalValue present RemoteValue NULL

    Finding An index exists on one server but not the other. The optimizer on the side without it couldn't pick the same plan. Create the missing index there to compare apples to apples.

    Unbenutzter Index

    Was sie beweistDie Indexlisten der Tabelle unterscheiden sich. Nicht, dass die Abfrage den Index verwendet.

    GegenprobeDie Abfrage nutzt den Index gar nicht; die Meldung kam trotzdem mit Priorität 1.

  • 2KompilierungAusgabe des Werkzeugs (Original, englisch)

    Priority 1 Category Index Setting Present

    Object PlanCompareLab.dbo.Orders.IX_Orders_Customer

    LocalValue NULL RemoteValue present

    Finding An index exists on one server but not the other. The optimizer on the side without it couldn't pick the same plan. Create the missing index there to compare apples to apples.

    Priority 1 Category Index Setting Present

    Object PlanCompareLab.dbo.Orders.IX_Orders_Customer_Renamed

    LocalValue present RemoteValue NULL

    Finding An index exists on one server but not the other. The optimizer on the side without it couldn't pick the same plan. Create the missing index there to compare apples to apples.

    Nur umbenannt

    Was sie beweistDie Indexnamen unterscheiden sich.

    GegenprobeTechnisch derselbe Index unter neuem Namen. Verglichen werden Namen, nicht Definitionen.

  • 1KontextAusgabe des Werkzeugs (Original, englisch)

    Priority 1 Category Object Setting Present

    Object PlanCompareLab.dbo.Orders

    LocalValue NULL RemoteValue present

    Finding A table referenced by the plan is missing on one server. This will break the query entirely.

    Priority 1 Category Object Setting Present

    Object PlanCompareOther.dbo.Orders

    LocalValue present RemoteValue NULL

    Finding A table referenced by the plan is missing on one server. This will break the query entirely.

    Andere Datenbanknamen

    Was sie beweistUnter diesem vollständigen Namen gibt es die Objekte auf einer Seite nicht.

    GegenprobeDie Datenbank hieß auf der anderen Seite anders. Daraus entstehen scheinbare Lücken bei allen Objekten.

  • 2KompilierungAusgabe des Werkzeugs (Original, englisch)

    Priority 1 Category ForcedPlan Setting QueryStore

    Object PlanCompareLab

    LocalValue plan_id=35 hash=0x01B30C54B38BF8D7 RemoteValue NULL

    Finding A forced plan exists on one side. Forced plans override the optimizer and almost always cause cross-server divergence.

    Fremder Forced Plan

    Was sie beweistIn dieser Datenbank ist irgendein Plan erzwungen.

    GegenprobeErzwungen war der Plan einer unbeteiligten Prozedur. Für unsere Abfrage war nichts erzwungen.

Lab-Gegenproben auf SQL Server 2022; die Ergebniszeilen stehen wörtlich und unübersetzt auf den Vorderseiten, Priorität und Texte sind die vom Werkzeug gemeldeten. Bei den Karten mit zwei Zeilen sind es die beiden zugehörigen Meldungen derselben Ausgabe.
  • Unbenutzter Index, Priorität 1. Wir legten einen Index an, den die Abfrage gar nicht verwendet. Das Werkzeug meldete ihn mit höchster Priorität. Es betrachtet alle Indexe der beteiligten Tabellen.
  • Nur umbenannt. Ein technisch gleicher Index unter neuem Namen ergab zwei Zeilen Index · Present, eine für den alten und eine für den neuen Namen, jeweils nur auf einer Seite vorhanden. Verglichen werden Namen, nicht Definitionen.
  • Andere Datenbanknamen. Heißt die Datenbank auf beiden Seiten anders, entstehen scheinbare Lücken bei allen Objekten.
  • Fremder Forced Plan. Ein im Query Store erzwungener Plan einer unbeteiligten Prozedur in derselben Datenbank erzeugte ebenfalls eine Meldung mit Priorität 1. Für unsere Abfrage war nichts erzwungen.

Daraus folgt keine Kritik am Werkzeug, sondern eine Lesehilfe. Die Index-Meldung schlägt vor, den Index auf der anderen Seite anzulegen, damit beide Seiten wirklich vergleichbar sind. Das ist ein Rat für den Vergleich, kein Befund über die Ursache. Bevor daraus in Produktion ein CREATE INDEX wird, prüfen wir Definition, Nutzung und die tatsächliche Arbeitslast. Und eine fehlende Zeile heißt nicht, dass beide Seiten gleich sind (siehe 25-%-Regel in der Vertiefung).

Vertiefung für den regelmäßigen Einsatz

Vertiefung

Für alle, die das Werkzeug regelmäßig einsetzen wollen. Für den Prüfweg unten sind diese Abschnitte nicht nötig. Zum Prüfweg springen ↓

Drei Uhren, drei Messgrenzen

Das Werkzeug zeigt Zeiten aus drei Quellen, und alle sind in Millisekunden. Sie messen trotzdem verschiedene Dinge:

  • Letzter Actual Plan: eine einzelne, die zuletzt verfügbare Ausführung. Ab SQL Server 2019 über LAST_QUERY_PLAN_STATS abrufbar, wenn die Option eingeschaltet ist. Ist sie aus, nutzt das Werkzeug den geschätzten Plan; fehlende Laufzeitwarnungen beweisen dann nichts (Microsoft zu sys.dm_exec_query_plan_stats).
  • Query Store: Summen über gespeicherte Zeitintervalle. Das Werkzeug addiert die Wartezeiten zum Plan über alle vorhandenen Intervalle. Ein Zeitraum mit vielen Ausführungen hat dann mehr Gesamtwartezeit, auch wenn jede einzelne schneller war. Fair wird der Vergleich erst mit gleichem Zeitfenster und Ausführungszahl; die Wartekategorien sind außerdem gröber als Wartetypen (Microsoft zu Query-Store-Waits).
  • Sitzung: kumulierte Wartezeiten der Verbindung. Im Lab wartete eine Sitzung erst 1,2 Sekunden per WAITFOR und startete dann die Abfrage, die an einer Sperre hängen blieb. Das Werkzeug zeigte dabei die 1.200 ms des früheren WAITFOR. Sie gehören zur Geschichte der Sitzung, nicht zur Abfrage.
Bild 9SchemaLab-Wert 1.200 ms4Laufbedingungen

Drei Uhren

Schema; nur die 1.200 ms sind ein Lab-Wert. Alle drei Uhren zeigen Millisekunden, aber mit verschiedenen Messgrenzen: eine Ausführung, Summen über Intervalle, die ganze Geschichte der Sitzung. Striche im Query Store: gezählte Ausführungen (Illustration).

Ein Befund mit offener Ursache. Für einen gezielt blockierten Lauf von rund 2,4 Sekunden lieferten die Quellen auf unseren Builds Folgendes:

QuelleSQL Server 2022 CU27 (16.0.4295.3)SQL Server 2025 CU9 (17.0.5005.3)
DMV-Dauer der letzten Ausführung2.424 ms2.421 ms
Explizit erfasster Actual Plan (Dauer / CPU)2.423 / 24 ms2.420 / 23 ms
Letzter Actual Plan (Dauer / CPU)1 / 1 ms24 / 2.420 ms
Ausgabe des Werkzeugs (Dauer / CPU)1 / 1 ms2.420 / 24 ms

Auf SQL Server 2025 sind Dauer und CPU im letzten Actual Plan vertauscht. Brent hatte genau diesen Fehler im April beschrieben (Brents Fehlerbericht), und sein Werkzeug tauscht die Werte beim Lesen zurück; das Ergebnis passt. Auf unserem 2022-Build stehen dagegen 1 ms, wo 2,4 Sekunden gemessen wurden; das erklärt der Tausch nicht. Die Ursache haben wir nicht geklärt. Praktische Konsequenz: Zeiten aus dem letzten Actual Plan mit Build-Nummer notieren und gegen eine zweite Quelle prüfen.

Dieselbe Abfrage, zwei Cachekopien

Der Query Hash ist ein Fingerabdruck der Abfragelogik, der Plan Hash einer der Planform. Die Frage „dieselbe Abfrage?“ und die Frage „derselbe Arbeitsweg?“ haben also verschiedene Kennzeichen (Microsoft zu sys.dm_exec_query_stats). Beide reichen nicht immer, um einen Cacheeintrag eindeutig zu bestimmen: Mit unterschiedlicher ARITHABORT-Einstellung entstanden zwei Kopien derselben Prozedur, mit gleichem Query Hash und gleichem Plan Hash. Der Aufruf blieb mehrdeutig; einen Parameter für das Plan Handle hat die getestete Fassung nicht (Microsoft zu Cacheattributen). Das ist übrigens auch die Mechanik hinter dem Klassiker „in SSMS schnell, in der Anwendung langsam“ (Microsoft dazu).

Und: Das Werkzeug sucht den Ausgangsplan im Plan Cache. Nachdem wir den Cache unserer Datenbank geleert hatten, lag der Plan noch im Query Store, wurde aber nicht mehr gefunden. Query Store ist in der getesteten Fassung kein Ersatz für einen verdrängten Cacheplan. Auch das führt das Skript selbst als bekannte Grenze dieser Version.

Die 25-%-Regel

Bei bestimmten Zahlenwerten blendet das Werkzeug kleine Unterschiede aus. Gerechnet wird mit dem größeren der beiden Werte als Basis: 100 gegenüber 125 sind 25/125 = 20 % und werden unterdrückt; 100 gegenüber 150 sind 50/150 ≈ 33 % und werden gemeldet. Genau 25 % werden ebenfalls unterdrückt. Keine Meldung heißt also nicht „gleich“.

Bild 10Modellzahlen

25-%-Rechner

Differenz 50 ÷ größerer Wert 150 ≈ 33 % · gemeldet

A gegenüber BDifferenz ÷ größerer WertErgebnis
100 gegenüber 12525/125 = 20 %unterdrückt
100 gegenüber 15050/150 ≈ 33 %gemeldet
100 gegenüber 7525/100 = 25 %unterdrückt (genau 25 %)
Modellzahlen. Die Regel gilt im Werkzeug nur für bestimmte Zahlenwerte, nicht für alle Kategorien. Keine Meldung heißt nicht „gleich“.

Vom Befund zur Wirkung: der Prüfweg

Am Ende steht ein einfacher Ablauf, der auch ohne das Werkzeug funktioniert:

  1. Symptom festhalten. Welche Ausführung ist langsam, mit welchem Parameter, wann, aus welcher Anwendung? Dabei Serverdauer und Zeit bis zur Anzeige im Client nicht verwechseln.
  2. Spuren sammeln. Hier spart sp_BlitzPlanCompare die meiste Zeit.
  3. Hypothese formulieren. Aus der Zeile Parameter · CompiledValue (1 gegenüber 42) wird: Der für wenige Treffer kompilierte Plan macht beim großen Kunden zu viele Lookups. Aus „LCK_M_S“ wird: Der Leser wartet auf eine bestimmte Transaktion.
  4. Gegenprobe. Eine begründete Größe ändern, Ergebnisgleichheit und Arbeit messen. Widerlegt? Zurück zur nächsten Spur.
  5. Wirkung und Kosten. Hilft die Änderung auch für andere Parameter, unter Schreiblast, im Betrieb?
Bild 11Schema

Der Prüfweg, am Beispiel aus Fall 1

  1. 1SymptomDer Aufruf für den großen Kunden ist in Produktion langsam.
  2. 2Spurensp_BlitzPlanCompare meldet Parameter · CompiledValue: @CustomerID 1 gegenüber 42.
  3. 3HypotheseDer für wenige Treffer kompilierte Plan macht beim großen Kunden zu viele Lookups.
  4. 4GegenprobeGleicher Laufzeitwert, nur der Compile-Wert geändert: rund 270.000 statt 2.800 Seitenzugriffe, gleiches Ergebnis.
  5. 5WirkungGegenmittel an echter Last, Schreiblast und Betriebsaufwand messen, auch für andere Parameter.

Gegenprobe widerlegt? Zurück zu den Spuren, zur nächsten.

Schema. Der Weg funktioniert auch ohne das Werkzeug; sp_BlitzPlanCompare verkürzt vor allem Schritt 2.

Der Snapshot ist dabei Teil der Belege: mit Zeitpunkt aufbewahren und vor einer Weitergabe auf Inhalte prüfen.

Herkunft und Dank

sp_BlitzPlanCompare stammt von Brent Ozar und ist Teil des SQL Server First Responder Kit (MIT-Lizenz). Das Werkzeug macht eine mühsame Aufgabe greifbar: Die Untersuchung beginnt bei einer konkreten Abfrage und legt Parameter, Konfiguration, Tabellenmetadaten und aktuelle Zustände gezielt nebeneinander. Danke an Brent und die Community rund um das First Responder Kit.

Dieser Beitrag ist eine eigene Darstellung mit eigenen Beispielen und Messungen. Brent hat ihn weder geprüft noch freigegeben; Schlussfolgerungen und eventuelle Fehler sind unsere. Für das Werkzeug selbst sind Brents Vorstellung und die Dokumentation im Repository die richtigen Einstiege. Neuere Fassungen können Funktionen und Grenzen verändern.

Methode und Grenzen

Getestete Fassung: OptionalScripts/sp_BlitzPlanCompare.sql, Commit 53c380ed vom 21.10.2026, unverändert.

Aufbau und Messung im Detail

Aufbau: je zwei eigene Instanzen SQL Server 2022 CU27 (16.0.4295.3) und 2025 CU9 (17.0.5005.3) unter Linux in Containern, je 2 CPUs und 2 GB Max Server Memory; ergänzende Funktionsproben auf 2017 und 2019. Startkonfiguration Compatibility Level 140, MAXDOP 2, Cost Threshold 50, Query Store an, LAST_QUERY_PLAN_STATS ab 2019 bewusst eingeschaltet. Rein synthetische Daten: 100.000 Aufträge, 90.000 davon für einen Kunden, zehn je seltenem Kunden.

Messung: Parameterkreuzprobe mit gezielter Neukompilierung je Zelle, danach fünf warme Läufe; Reads in jeder Zelle stabil. CPU- und Dauerwerte sind wegen geteilter VM-Ressourcen nur illustrativ; die Readszahlen tragen die Aussage. 51 datenbasierte Prüfungen über alle Proben bestanden.

Nicht geprüft: Azure SQL Database, Managed Instance, Windows-Engine, eingeschränkte Rechte, ein eigenes Histogramm-Beispiel (das Werkzeug vergleicht in dieser Fassung nur Statistikköpfe), große Plan Caches, Overhead unter realer Last, PSP bei Compatibility Level 160/170, Spills und Memory Grants als eigener Positivfall, viele Sonderfälle mit mehreren Statements. Die Zahlen gehören zu unserem synthetischen Aufbau und sind keine Erwartungswerte für andere Datenbanken.

Quiz

Lesen Sie die Liste richtig?

Jede Antwort wird sofort aufgelöst, mit Verweis auf die passende Stelle im Beitrag. Ohne Cookies.

Weiterlesen