Ratgeber · SQL Server Performance

SQL Server ist mal schnell und mal langsam

Dieselbe Abfrage ist morgens schnell und mittags langsam? Dieser Diagnoseweg ist Teil unseres Ratgebers SQL Server Performance. Er zeigt, wie Sie einen schnellen und einen langsamen Zustand vergleichen und herausfinden, ob ein anderer Plan, andere Parameter oder andere Bedingungen dahinterstecken.

Stand 21.10.2026 · ca. 12 Minuten

Abfrage Query StorePläne zum UmschaltenLab SQL Server 2022
Plan für 10 Treffer kompiliertSeek + 900.000 Key Lookups2,7 Mio. Seiten · 1.882 msPlan für 900.000 Treffer kompiliertein Clustered Index Scan28.700 Seiten · 211 msGleiche Abfrage, gleiche 900.000 Zeilen:fast 100-mal so viele Lesezugriffe.

Schwankende Performance kann verschiedene Ursachen haben: Ein anderer Parameter verlangt mehr Arbeit. SQL Server verwendet einen anderen Ausführungsplan. Oder derselbe Plan läuft, während andere Vorgänge Ressourcen oder Sperren belegen.

Der erste Vergleich lautet deshalb: Führt SQL Server andere Arbeit aus oder dauert vergleichbare Arbeit unter anderen Bedingungen länger?

Warum dieselbe Abfrage unterschiedliche Ausführungspläne haben kann

Ein Ausführungsplan beschreibt, wie SQL Server eine Abfrage bearbeitet: welche Zugriffe er verwendet, wie er Tabellen verbindet und welche Arbeitsschritte folgen.

Beim Kompilieren schätzt der Optimizer unter anderem, wie viele Zeilen zu verarbeiten sind. Bei vielen parametrisierten Abfragen kann er dafür die aktuellen Parameterwerte berücksichtigen. Das wird Parameter Sniffing genannt. Ein bereits kompilierter Plan kann anschließend für weitere Aufrufe wiederverwendet werden.

Das spart Kompilierungsarbeit. Es kann aber ungünstig werden, wenn sich die Datenmengen stark unterscheiden: Ein Zugriff, der für einen Kunden mit zehn passenden Zeilen wenig Arbeit verursacht, kann bei einem anderen Kunden mit 900.000 passenden Zeilen teuer werden. Der Parameterwert wechselt; der wiederverwendete Plan muss deshalb nicht ebenfalls wechseln. Microsoft erklärt Parameterwerte bei der Kompilierung und Planwiederverwendung.

Parameter Sniffing ist damit eine mögliche Erklärung. Um sie für Ihren Fall zu prüfen, vergleichen Sie zunächst die tatsächlichen Aufrufe.

Schritt 1: Einen schnellen und einen langsamen Aufruf vergleichen

Wählen Sie dieselbe fachliche Aufgabe und notieren Sie für beide Zustände:

  • Zeitpunkt und gemessene Dauer;
  • verwendete Filter und Parameter, etwa Kundennummer oder Datumsbereich;
  • Umfang der angeforderten und zurückgegebenen Daten;
  • betroffene Anwendung und Datenbank;
  • bekannte Änderungen und gleichzeitig laufende größere Aufgaben.

Prüffrage: Vergleichen Sie wirklich dieselbe Arbeit? Eine Suche über einen Tag und eine Suche über fünf Jahre können denselben SQL-Text verwenden und dennoch sehr unterschiedlich viel verarbeiten.

„In SSMS ist es schnell“

Diesen Satz hört man oft. Zwei Ursachen sind besonders häufig:

  1. Andere Verbindungseinstellungen. SQL Server Management Studio setzt ARITHABORT ON, viele Anwendungstreiber arbeiten mit ARITHABORT OFF. Weil diese Einstellung zum Schlüssel des Plancaches gehört, bekommt dieselbe Abfrage aus SSMS einen eigenen Cacheeintrag und wird mit Ihrem Testwert neu kompiliert. Sie sehen dann einen anderen Plan als die Anwendung.
  2. Lokale Variablen statt Parameter. Wer die Abfrage zum Testen mit DECLARE @Kunde int = 42; nachbaut, misst etwas anderes: Den Wert einer lokalen Variablen kennt der Optimizer beim Kompilieren nicht. Er rechnet mit einer durchschnittlichen Schätzung, ähnlich wie bei OPTIMIZE FOR UNKNOWN.

Testen Sie deshalb mit denselben Einstellungen und echten Parametern, etwa über den Aufruf der Procedure oder sp_executesql. Prüfen Sie außerdem, ob in beiden Fällen dieselbe Datenbank verwendet und das vollständige Ergebnis abgerufen wird.

Ihr erster Befund: Sie können beschreiben, welche Bedingungen gleich waren und welche noch voneinander abweichen. Fehlende Parameterwerte bleiben eine offene Frage.

Schritt 2: Die vorhandene Historie im Query Store prüfen

Der Query Store kann frühere Pläne und Laufzeitstatistiken einer Datenbank aufbewahren. Damit lässt sich untersuchen, ob sich eine Abfrage seit einem bestimmten Zeitraum anders verhält.

Prüfen Sie zuerst, ob er überhaupt Daten hat. Ab SQL Server 2022 ist Query Store für neu angelegte Datenbanken standardmäßig eingeschaltet. Ältere Versionen und Datenbanken, die von dort migriert wurden, haben ihn oft nicht aktiv.

Öffnen Sie in SQL Server Management Studio die Eigenschaften der betroffenen Datenbank und dort Query Store. Prüfen Sie den tatsächlichen Betriebszustand. Steht er unerwartet auf Read Only, ist häufig die maximale Größe erreicht. Vorhandene Daten bleiben dann lesbar, neue werden nicht mehr gesammelt. Den Grund zeigt die Spalte readonly_reason. Microsoft beschreibt die Query-Store-Zustände.

Wenn Historie vorhanden ist:

  1. Öffnen Sie unter der Datenbank den Bereich Query Store.
  2. Beginnen Sie bei einer beobachteten Verschlechterung mit Regressed Queries und grenzen Sie die Vergleichszeiträume ein.
  3. Identifizieren Sie das zur betroffenen Aufgabe gehörende Statement. Prüfen Sie den SQL-Text und gegebenenfalls die aufrufende Procedure.
  4. Sehen Sie nach, welche Pläne im schnellen und im langsamen Zeitraum verwendet wurden. Wählen Sie zwei Pläne aus, kann der Bericht sie grafisch gegenüberstellen. Microsoft erläutert den Bericht Regressed Queries.

Ein leerer Bericht beweist nicht, dass es keine langsamen Ausführungen gab. Zeitraum, Filter und Erfassungsumfang können den gesuchten Fall ausblenden. Im üblichen Erfassungsmodus AUTO lässt Query Store zum Beispiel seltene und sehr billige Abfragen aus.

Abfrage: Teuerste Abfragen im Zeitfenster

Statt der Berichte können Sie Query Store auch direkt abfragen. Die folgende Abfrage listet für ein Zeitfenster die Abfragen mit dem höchsten CPU-Verbrauch. Führen Sie sie einmal für den schnellen und einmal für den langsamen Zeitraum aus.

Abfrage 1: Teuerste Abfragen im Zeitfenster · nur lesend · geprüft auf SQL Server 2017–2025
/* PSG Stack · SQL Server Performance Ratgeber · Abfrage 1
   Query Store: Welche Abfragen haben im Zeitfenster am meisten CPU verbraucht?

   - Nur lesend. Ändert keine Einstellungen und keine Daten.
   - Läuft mit MAXDOP 1, damit die Diagnose den Server nicht zusätzlich
     mit paralleler Arbeit belastet.
   - In der betroffenen Datenbank ausführen (USE ...). Query Store muss dort
     im fraglichen Zeitraum Daten gesammelt haben.
   - Rechte: VIEW DATABASE STATE
     (ab SQL Server 2022 genügt VIEW DATABASE PERFORMANCE STATE).
     Ohne diese Rechte bricht die Abfrage mit Fehler 262 ab.
     Ohne Rechte am Objekt (z. B. VIEW DEFINITION) bleibt die Spalte objekt leer.
   - Ab SQL Server 2016. Geprüft im PSG-Lab auf 2017, 2019, 2022 und 2025.
   - Zeitangaben mit Zeitzone eintragen; Query Store speichert in UTC.
   - Werte sind über Query-Store-Intervalle zusammengefasst, keine Einzelaufrufe.
*/
DECLARE @von datetimeoffset = '2026-10-21 08:00 +02:00',   -- Beginn des Zeitfensters
        @bis datetimeoffset = '2026-10-21 09:00 +02:00';   -- Ende des Zeitfensters

SELECT TOP (20)
    q.query_id,
    OBJECT_NAME(q.object_id)                                       AS objekt,
    COUNT(DISTINCT rs.plan_id)                                     AS plaene,
    SUM(rs.count_executions)                                       AS ausfuehrungen,
    SUM(CASE WHEN rs.execution_type <> 0
             THEN rs.count_executions ELSE 0 END)                  AS abgebrochen_oder_fehler,
    CAST(SUM(rs.avg_cpu_time * rs.count_executions) / 1000.0
         AS decimal(18,1))                                         AS cpu_ms_gesamt,
    CAST(SUM(rs.avg_cpu_time * rs.count_executions)
         / NULLIF(SUM(rs.count_executions), 0) / 1000.0
         AS decimal(18,2))                                         AS cpu_ms_je_ausfuehrung,
    CAST(SUM(rs.avg_duration * rs.count_executions)
         / NULLIF(SUM(rs.count_executions), 0) / 1000.0
         AS decimal(18,2))                                         AS dauer_ms_je_ausfuehrung,
    CAST(SUM(rs.avg_logical_io_reads * rs.count_executions)
         / NULLIF(SUM(rs.count_executions), 0)
         AS decimal(18,0))                                         AS seiten_gelesen_je_ausfuehrung, -- logische Reads, 8-KB-Seiten
    LEFT(qt.query_sql_text, 200)                                   AS sql_text_anfang
FROM sys.query_store_runtime_stats AS rs
JOIN sys.query_store_runtime_stats_interval AS i
     ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id
JOIN sys.query_store_plan AS p        ON p.plan_id = rs.plan_id
JOIN sys.query_store_query AS q       ON q.query_id = p.query_id
JOIN sys.query_store_query_text AS qt ON qt.query_text_id = q.query_text_id
WHERE i.start_time < @bis
  AND i.end_time   > @von
GROUP BY q.query_id, q.object_id, qt.query_sql_text
ORDER BY cpu_ms_gesamt DESC
OPTION (MAXDOP 1);

Beispielausgabe aus unserem Lab (SQL Server 2022, gekürzt auf die wichtigsten Spalten und Zeilen):

Beispielausgabe
query_id objekt   plaene ausfuehrungen abgebrochen_oder_fehler cpu_ms_gesamt cpu_ms_je_ausfuehrung seiten_gelesen_je_ausfuehrung
5        p_teuer  1      5             0                       5618.2        1123.63               415586
6        p_lang   1      2             2                       3998.5        1999.23               425956
3        p_billig 1      50            0                       .8            .02                   3
7        p_fehler 1      3             3                       .7            .23                   3

p_teuer führt mit rund 5,6 Sekunden CPU. p_billig lief zehnmal so oft, ist aber unauffällig. p_lang wurde beide Male durch einen Timeout der Anwendung abgebrochen, p_fehler endete dreimal mit einem Fehler.

So lesen Sie das Ergebnis:

  • plaene größer als 1: Für diese Abfrage gab es im Fenster mehrere Pläne. Ein Kandidat für Schritt 3.
  • cpu_ms_je_ausfuehrung und seiten_gelesen_je_ausfuehrung zeigen den Aufwand je Aufruf; ausfuehrungen zeigt, wie oft. Steigt im langsamen Zeitraum vor allem die Aufrufzahl, hilft der Diagnoseweg „Unter Last“ weiter.
  • abgebrochen_oder_fehler zählt Ausführungen, die abgebrochen wurden, etwa durch einen Timeout der Anwendung, oder mit einem Fehler endeten. Häufen sie sich, lohnt der Diagnoseweg zu Blocking und Timeouts.

Vergleichen Sie je Plan und Zeitraum mindestens Dauer, CPU-Zeit, logische Lesezugriffe und Aufrufzahl. Logische Lesezugriffe zählen gelesene 8-KB-Seiten im Speicher; sie sind keine Zahl physischer Plattenzugriffe. Achten Sie auf die Einheiten: Die Katalogsichten speichern Dauer und CPU in Mikrosekunden und Lesezugriffe als Seitenzahl, die SSMS-Berichte zeigen Millisekunden und Kilobyte. Die Aufrufzahl hilft, einen veränderten Gesamtverbrauch einzuordnen. Microsoft dokumentiert die Laufzeitkennzahlen.

Query Store fasst Messwerte in Zeitintervallen zusammen. Ein Durchschnitt von 100 Millisekunden bedeutet daher nicht, dass jeder einzelne Aufruf 100 Millisekunden dauerte. Die Parameterwerte jedes einzelnen Aufrufs lassen sich daraus nicht rekonstruieren. So sammelt Query Store seine Daten.

Wenn der gesuchte Zeitraum nicht erfasst wurde, halten Sie diese Lücke fest. Das spätere Einschalten einer Erfassung kann die vergangene Störung nicht nachträglich aufzeichnen.

Schritt 3: Ausführungspläne und Parameter zusammen betrachten

Wenn sich die Pläne unterscheiden, prüfen Sie, ob die Unterschiede zum beobachteten Aufwand passen.

Ein häufiger Fall ist ein Index Seek mit Key Lookups. SQL Server findet passende Einträge gezielt in einem Index und holt zusätzlich benötigte Spalten über weitere Einzelzugriffe. Für wenige Treffer ist das günstig. Bei sehr vielen Treffern können diese Einzelzugriffe teurer werden als ein Scan, also das vollständige Lesen der Tabelle oder des Index.

Prüfen Sie deshalb:

  1. Wie viele passende Zeilen wurden beim maßgeblichen Zugriff geschätzt?
  2. Wie viele Zeilen wurden tatsächlich verarbeitet? Das zeigt nur ein Actual Plan, also ein tatsächlicher Ausführungsplan mit gemessenen Zeilenzahlen.
  3. Für welchen Parameterwert wurde der Plan kompiliert, und mit welchem lief der langsame Aufruf?
  4. Erklärt der Zugriffsweg die zusätzlichen Reads oder die höhere CPU-Arbeit?

Frage 3 beantwortet der Plan selbst. Klicken Sie im grafischen Plan auf den obersten Operator (zum Beispiel SELECT) und öffnen Sie die Eigenschaften. Unter Parameter List steht für jeden Parameter der Parameter Compiled Value, also der Wert, mit dem der Optimizer geschätzt hat. In einem Actual Plan steht daneben der Parameter Runtime Value des gemessenen Aufrufs. Liegen beide weit auseinander und passt die Schätzung nur zum ersten Wert, haben Sie einen deutlichen Hinweis auf Parameter Sniffing.

Der gespeicherte Query-Store-Plan enthält ebenfalls den kompilierten Parameterwert und die Schätzungen. Er ist aber kein Actual Plan mit den tatsächlichen Zeilenzahlen der damaligen Ausführungen. Ein neu aufgezeichneter Actual Plan gehört zu seiner eigenen Ausführung. Microsoft beschreibt die gespeicherten Pläne im Query Store.

Beispiel aus unserem Versuch mit unterschiedlichen Parametern

In einem synthetischen Versuch enthielt dieselbe Tabelle für einen Kunden zehn passende Zeilen, für einen anderen 900.000. Die Abfrage berechnete jeweils einen Summenwert. Viele verarbeitete Tabellenzeilen bedeuteten hier also nicht viele zurückgegebene Ergebniszeilen.

Für den Kunden mit 900.000 passenden Zeilen verglichen wir zwei Planherkünfte. Schalten Sie zwischen den beiden tatsächlichen Plänen um:

2.702.000 Seiten gelesen1.882 ms Clientdauer (Median)
Tatsächliche Pläne aus unserem Lab (SQL Server 2022), beide für den Kunden mit 900.000 Zeilen ausgeführt. Je Knoten: geschätzte → tatsächliche Zeilen.
Verwendeter PlanLogische Lesezugriffe je Aufruf, ungefährClientdauer, Median
Zuvor für den kleinen Fall kompiliert: Seek mit Lookups2.702.0001.882 ms
Für den großen Fall kompiliert: Scan28.700211 ms

Der erste Plan schätzte zehn Treffer und holte jede der 900.000 Zeilen einzeln nach. Der zweite war für die große Treffermenge kompiliert und las die Tabelle einmal durch. Das entscheidende Maß ist die Arbeitsmenge: fast hundertmal so viele Lesezugriffe. Für den kleinen Fall war der Scan dagegen unnötig aufwendig: 89 statt unter 1 Millisekunde.

Was Sie daraus für Ihre Diagnose mitnehmen können: Ein Seek ist kein automatisches Gütesiegel. Entscheidend ist, wie viel Arbeit der konkrete Plan mit den aktuellen Daten und Parametern verursacht.

Auf SQL Server 2022 und neuer mit Kompatibilitätsgrad 160 kann SQL Server für solche Fälle selbst mehrere Planvarianten bilden (Parameter Sensitive Plan Optimization, kurz PSP). Im Query Store erscheinen sie dann als Varianten derselben Abfrage. Wer unser Beispiel auf einem aktuellen System nachstellt, sieht deshalb möglicherweise ein anderes Bild.

Eine ausführlichere Erklärung mit Beispielen finden Sie unter Parameter Sniffing in SQL Server verstehen.

Läuft dieselbe Abfrage auf einem anderen Server schnell, etwa im Test, zeigt unser Beitrag sp_BlitzPlanCompare: Test schnell, Produktion langsam, wie Sie Pläne, Parameter und Statistiken beider Server vergleichen und einen Unterschied mit einer Gegenprobe als Ursache belegen.

Schritt 4: Bei gleichem Plan nach veränderten Bedingungen suchen

Ein unveränderter Plan kann unterschiedliche Laufzeiten haben. Ordnen Sie weitere Beobachtungen ein:

BeobachtungNächster Prüfschritt
Die Zahl verarbeiteter Zeilen oder die Reads steigenParameter, Datenmenge und Zugriffsarbeit vergleichen
Die Dauer steigt stark, die CPU-Arbeit je Aufruf bleibt ähnlichZeitgleiche Wartezeiten und mögliche Blockierungen untersuchen
Viele Abfragen werden im gleichen Zeitraum langsamerGemeinsame Last, Ressourcen und konkurrierende Jobs prüfen (Diagnoseweg „Unter Last“)
SQL Server beendet den Aufruf schnell, die Anwendung wartet weiterWeitere SQL-Aufrufe, Übertragung und Ergebnisverarbeitung zeitlich zuordnen

Eine lange Dauer bei wenig CPU passt zum Beispiel zu Blocking, beweist es aber noch nicht. Dafür brauchen Sie einen zeitlich passenden Beleg, wer auf welche Ressource gewartet hat.

Achten Sie außerdem auf verschachtelte Aufrufe: Ein äußeres Statement kann die Zeit eines Triggers oder einer Funktion enthalten. Die Zeiten der inneren und äußeren Statements lassen sich dann nicht einfach zu einer Gesamtdauer addieren. Unser Beitrag Query Store bei Triggern, Procedures und Funktionen zeigt solche Messgrenzen an eigenen Beispielen.

Schritt 5: Die nächste Maßnahme aus dem Befund ableiten

Jetzt sollte Ihre Erklärung genauer sein als „SQL Server ist manchmal langsam“.

Wenn Plan und Parameter zusammen den Mehraufwand erklären, kommen mehrere Gegenmaßnahmen in Frage. Jede hat ihren Preis:

MaßnahmeWas sie bewirktWorauf Sie achten müssen
Passender Index, etwa mit INCLUDE-SpaltenMacht den günstigen Zugriff für beide Fälle billigerZusätzlicher Speicher und Schreibaufwand
Geänderte AbfrageformTrennt zum Beispiel kleine und große Fälle in eigene AbfragenMehr Code, der gepflegt werden will
OPTION (RECOMPILE)Kompiliert bei jedem Aufruf mit dem aktuellen WertKompilierkosten bei jedem Aufruf; bei sehr häufigen Aufrufen spürbar
OPTIMIZE FOR (@Parameter = Wert)Legt einen festen Schätzwert festPasst nur, solange dieser Wert repräsentativ bleibt
Plan Forcing oder Query-Store-Hints (ab SQL Server 2022)Hält einen bekannten Plan oder Hinweis fest, ohne den Code zu ändernMuss beobachtet und bei Datenänderungen überprüft werden

In unserem Parameter-Lab passte OPTION (RECOMPILE) in beide Richtungen: rund 212 Millisekunden im großen und 2,4 Millisekunden im kleinen Fall, statt unter 1 Millisekunde ohne Neukompilierung. OPTIMIZE FOR UNKNOWN ist dagegen kein allgemeiner Schalter gegen schwankende Performance: Dort führte die durchschnittliche Schätzung im großen Fall weiterhin zu vielen Lookups. Im Test müssen deshalb immer kleine und große Parameterfälle vorkommen.

Prüfen Sie zudem, ob ein Plan bereits erzwungen wird. Plan Forcing bedeutet, dass SQL Server einen festgelegten Plan verwenden soll. Halten Sie fest, warum und seit wann diese Vorgabe besteht und ob sie noch zur aktuellen Situation passt.

In einem anonymisierten Praxisfall war eine neue Statistik vorhanden, die erwartete Verbesserung blieb zunächst aus. Ein zuvor erzwungener Plan hielt den alten Zugriffsweg fest. Nach Aufheben der Vorgabe und Beobachtung der Entwicklung konnte ein anderer Plan verwendet werden. Die mittlere Laufzeit der untersuchten Funktion sank von rund 15 auf 0,2 Millisekunden. Das war eine gemessene Funktionslaufzeit, keine Messung der vollständigen Anwendungsantwort. Der Praxisfall mit Statistik und erzwungenem Plan erläutert den Verlauf.

Für Ihren eigenen Fall gilt: Erst den vorhandenen Zustand und die Vergleichsdaten sichern, dann einen begrenzten Test planen. Zwei Abkürzungen führen dabei oft in die Irre:

  • Den Plancache leeren. DBCC FREEPROCCACHE ohne Argument entfernt die Pläne der ganzen Instanz; danach müssen alle Abfragen neu kompiliert werden, und Ihre Vergleichsgrundlage ist weg. Gezielt geht es mit dem plan_handle eines einzelnen Plans oder mit sp_recompile für eine Procedure.
  • „Das Statistikupdate hat geholfen.“ Ein Statistikupdate macht abhängige Pläne ungültig, die Abfrage wird neu kompiliert. Kommt dabei zufällig ein günstiger Parameterwert, wirkt es wie eine Lösung. Der nächste ungünstige Wert bringt das Problem zurück. Prüfen Sie deshalb nach jeder Maßnahme, mit welchem Wert der neue Plan kompiliert wurde.

Ein Neustart des SQL Servers ist dieselbe Abkürzung im Großen: Er verwirft alle Pläne, und der erste Aufruf danach entscheidet, welcher Plan bleibt. Warum das nur kurz hilft und was Sie vorher sichern sollten, zeigt Warum hilft ein Neustart, und warum nur kurz? an einem Labversuch.

Den Befund festhalten und die Wirkung prüfen

Ein brauchbarer Untersuchungsstand kann so aussehen:

Der langsame Aufruf verarbeitet eine deutlich größere Treffermenge. Er verwendet einen für wenige Treffer kompilierten Plan mit vielen zusätzlichen Zugriffen. Im Vergleich liegen wesentlich mehr logische Reads vor. Als Nächstes prüfen wir auf demselben Datenstand beide Parameterklassen mit einer gezielt geänderten Variante.

Nach einer Änderung vergleichen Sie erneut dieselbe fachliche Aufgabe. Prüfen Sie Dauer, CPU-Arbeit, Reads und Ergebnis sowie mögliche Auswirkungen auf andere Vorgänge. Wiederholen Sie die Beobachtung unter vergleichbaren Bedingungen. Ein einzelner schneller Aufruf nach der Änderung reicht nicht, um ein sporadisches Problem als gelöst zu betrachten.

Am Ende muss noch nicht jede Ursache bewiesen sein. Ein klarer Unterschied zwischen den beiden Zuständen und ein begründeter nächster Test bringen die Untersuchung bereits weiter.

Weitere Diagnosewege