Der SQL Server Query Store hilft dir, langsame Abfragen nicht nur im Moment ihres Auftretens zu untersuchen. Er speichert Ausführungspläne und zusammengefasste Laufzeitwerte über einen längeren Zeitraum. Dadurch kannst du nachvollziehen, ob eine Abfrage schon immer teuer war oder erst seit einem Planwechsel Probleme verursacht.

In diesem Beitrag prüfst du zunächst, ob der Query Store Daten sammelt. Danach findest du ressourcenintensive Abfragen, vergleichst ihre Pläne und entscheidest, wann das vorübergehende Erzwingen eines früheren Plans sinnvoll ist. Die Beispiele beziehen sich auf SQL Server ab Version 2016. Einzelne Ansichten und Berechtigungen unterscheiden sich je nach Version.

Was speichert der SQL Server Query Store?

Der Query Store erfasst unter anderem Abfragetexte, Ausführungspläne und Laufzeitstatistiken. Zu diesen Statistiken gehören beispielsweise Ausführungsanzahl, Dauer, CPU-Zeit und logische Lesezugriffe. Die Werte werden in Zeitintervallen zusammengefasst. Du erhältst damit eine Verlaufssicht, aber kein vollständiges Protokoll jeder einzelnen Ausführung.

Gerade bei Planregressionen ist dieser Verlauf nützlich. SQL Server kann für dieselbe Abfrage zu unterschiedlichen Zeiten verschiedene Ausführungspläne wählen. Ein geänderter Datenbestand, aktualisierte Statistiken, ein neuer Index oder eine geänderte Datenbankeinstellung können die Planwahl beeinflussen. Wird eine Abfrage nach einem solchen Wechsel deutlich langsamer, kannst du die Pläne und ihre Laufzeitwerte im Query Store gegenüberstellen.

Wenn du neben der Planwahl auch mögliche Indexprobleme untersuchen möchtest, lies ergänzend unseren Beitrag Indexanalyse im SQL Server mit Query Store, Ausführungsplänen und DMVs. Dort geht es darum, wie du eine konkrete Abfrage bewertest, bevor du einen Index anlegst oder änderst.

Voraussetzungen und Berechtigungen

Query Store ist seit SQL Server 2016 verfügbar. Bei neu angelegten Datenbanken unter SQL Server 2022 und neueren Versionen ist er standardmäßig aktiviert. Bei älteren, migrierten oder wiederhergestellten Datenbanken solltest du den tatsächlichen Zustand prüfen, statt dich auf die Voreinstellung zu verlassen.

Für die Abfrage der Query-Store-Sichten benötigst du unter SQL Server 2022 und neuer die Datenbankberechtigung VIEW DATABASE PERFORMANCE STATE. Bei SQL Server 2016 bis 2019 ist VIEW DATABASE STATE erforderlich. Einen Plan über Query Store zu erzwingen oder diese Erzwingung aufzuheben, erfordert ALTER auf der Datenbank.

Führe die Beispiele in der Datenbank aus, deren Abfragen du untersuchen möchtest. Ersetze DeineDatenbank und alle Beispiel-IDs durch Werte aus deiner Umgebung.

Schritt 1: Prüfen, ob Query Store Daten sammelt

Die folgende Abfrage zeigt den angeforderten und den tatsächlichen Betriebszustand sowie die aktuelle Größe des Query Store:

USE [DeineDatenbank];
GO

SELECT
    actual_state_desc,
    desired_state_desc,
    current_storage_size_mb,
    max_storage_size_mb,
    readonly_reason,
    query_capture_mode_desc
FROM sys.database_query_store_options;

Für eine laufende Analyse ist READ_WRITE als tatsächlicher Zustand wichtig. Steht dort READ_ONLY, kann Query Store keine neuen Laufzeitdaten aufnehmen. Vergleiche dann die aktuelle Größe mit der festgelegten Maximalgröße und prüfe readonly_reason. Eine ausgeschöpfte Speichergrenze oder eine für die Arbeitslast ungeeignete Konfiguration kann dazu führen, dass keine neuen Daten mehr gesammelt werden.

Ist Query Store ausgeschaltet, kann er für die betreffende Datenbank aktiviert werden:

ALTER DATABASE [DeineDatenbank]
SET QUERY_STORE = ON (OPERATION_MODE = READ_WRITE);

Das Aktivieren ändert die Datenbankkonfiguration. Stimme es deshalb bei produktiven Systemen mit deinem üblichen Änderungsprozess ab. Query Store benötigt außerdem erst ausgeführte Abfragen, bevor er einen brauchbaren Verlauf anzeigen kann. Für einen Vergleich „vorher gegen nachher“ müssen Daten aus beiden Zeiträumen vorhanden sein.

Schritt 2: Langsame Abfragen in SSMS finden

Der schnellste Einstieg führt häufig über SQL Server Management Studio. Öffne im Objekt-Explorer die betroffene Datenbank und darunter Query Store. Zwei Berichte sind besonders hilfreich:

  • Top Resource Consuming Queries: Zeigt Abfragen, die im gewählten Zeitraum besonders viele Ressourcen verbraucht haben. Du kannst beispielsweise nach Gesamtdauer, CPU-Zeit oder logischen Lesezugriffen suchen.
  • Regressed Queries: Zeigt Abfragen, deren Laufzeitwerte sich verschlechtert haben. Dort kannst du prüfen, ob die Verschlechterung mit einem anderen Ausführungsplan zusammenfällt.

Stelle zuerst den Zeitraum auf die Phase ein, in der Nutzer das Problem bemerkt haben. Wähle dann eine passende Messgröße. Eine seltene Abfrage mit hoher Einzeldauer ist für eine interaktive Anwendung möglicherweise kritisch. Eine kurze Abfrage, die tausendfach läuft, kann dagegen insgesamt mehr CPU-Zeit verbrauchen. Beides sind unterschiedliche Probleme und erfordern nicht zwangsläufig dieselbe Lösung.

Schritt 3: Teure Abfragen mit T-SQL auflisten

Wenn du die Ergebnisse weiterverarbeiten oder einen bestimmten Zeitraum wiederholt prüfen möchtest, kannst du die Query-Store-Sichten direkt abfragen. Das folgende Beispiel zeigt die zehn Abfragepläne mit der höchsten gesamten Ausführungsdauer innerhalb der letzten 24 Stunden. Es berücksichtigt nur regulär abgeschlossene Ausführungen.

USE [DeineDatenbank];
GO

WITH PlanStats AS
(
    SELECT
        p.query_id,
        p.plan_id,
        SUM(rs.count_executions) AS executions,
        SUM(rs.avg_duration * rs.count_executions)
            / 1000000.0 AS total_duration_s,
        SUM(rs.avg_duration * rs.count_executions)
            / NULLIF(SUM(rs.count_executions), 0)
            / 1000.0 AS avg_duration_ms,
        MAX(rs.last_execution_time) AS last_execution_time
    FROM sys.query_store_plan AS p
    INNER JOIN sys.query_store_runtime_stats AS rs
        ON rs.plan_id = p.plan_id
    INNER JOIN sys.query_store_runtime_stats_interval AS rsi
        ON rsi.runtime_stats_interval_id =
           rs.runtime_stats_interval_id
    WHERE rsi.start_time >= DATEADD(
        HOUR, -24, SYSDATETIMEOFFSET()
    )
      AND rs.execution_type = 0
    GROUP BY
        p.query_id,
        p.plan_id
)
SELECT TOP (10)
    ps.query_id,
    ps.plan_id,
    ps.executions,
    CAST(ps.total_duration_s AS decimal(18, 2))
        AS total_duration_s,
    CAST(ps.avg_duration_ms AS decimal(18, 2))
        AS avg_duration_ms,
    ps.last_execution_time,
    LEFT(qt.query_sql_text, 4000) AS query_text
FROM PlanStats AS ps
INNER JOIN sys.query_store_query AS q
    ON q.query_id = ps.query_id
INNER JOIN sys.query_store_query_text AS qt
    ON qt.query_text_id = q.query_text_id
ORDER BY ps.total_duration_s DESC;

Die Spalte avg_duration wird in Mikrosekunden gespeichert. Im Beispiel erfolgt die Umrechnung der durchschnittlichen Dauer in Millisekunden und der gesamten Dauer in Sekunden. Weil Query Store Werte pro Zeitintervall zusammenfasst, ist die Abfrage eine Auswertung dieser gesammelten Statistik und keine Liste einzelner Ausführungen.

Notiere dir für eine auffällige Zeile die query_id und plan_id. Prüfe anschließend, ob für dieselbe query_id weitere Pläne gespeichert sind. Eine hohe Gesamtdauer allein beweist noch keine Planregression: Vielleicht wurde die Abfrage häufiger ausgeführt oder musste plötzlich deutlich mehr Daten verarbeiten.

Schritt 4: Eine Planregression erkennen

Öffne im SSMS-Bericht Regressed Queries die betroffene Abfrage. Vergleiche ihre Pläne im Zeitraum vor und nach dem Leistungseinbruch. Achte dabei auf vier Fragen:

  1. Wann begann die Verschlechterung? Passt der Zeitpunkt zu einem Deployment, einem Statistik-Update oder einer Änderung an Indizes?
  2. Wurde ein neuer Plan verwendet? Eine zeitliche Übereinstimmung ist ein wichtiger Hinweis, aber noch kein Beweis für die Ursache.
  3. Wie viele Ausführungen liegen hinter den Werten? Ein Durchschnitt aus wenigen Ausführungen ist weniger belastbar als ein Vergleich über viele ähnliche Aufrufe.
  4. Welche Messgrößen änderten sich? Steigen neben der Dauer auch CPU-Zeit und logische Lesezugriffe, deutet das eher auf mehr Arbeit bei der Abfrage hin. Steigt vor allem die Dauer, solltest du zusätzlich Wartezeiten und äußere Einflüsse untersuchen.

Vergleiche anschließend die grafischen Ausführungspläne. Achte beispielsweise auf andere Join-Verfahren, Scans statt gezielter Zugriffe, Sortierungen oder stark veränderte Zeilenschätzungen. Der im Query Store gespeicherte Plan zeigt dir die gewählte Planstruktur. Für die Beurteilung einer konkreten Ausführung können zusätzlich ein tatsächlicher Ausführungsplan und weitere Messungen nötig sein.

Beispiel: Eine Abfrage verwendete bis Montag einen Plan mit durchschnittlich 80 Millisekunden. Seit Dienstag läuft sie mit einem anderen Plan im Durchschnitt 900 Millisekunden und verursacht deutlich mehr logische Lesezugriffe. Wenn Ausführungsanzahl und typische Parameter vergleichbar sind, ist das ein guter Kandidat für eine Planregression. Die Zahlen sind ein Beispiel; die Entscheidung in deiner Umgebung muss auf deinen Messwerten beruhen.

Schritt 5: Einen früheren Plan gezielt erzwingen

Wenn ein älterer Plan nachweislich besser funktioniert und noch im Query Store gespeichert ist, kannst du ihn vorübergehend erzwingen. Das geht im SSMS-Bericht über Force Plan oder mit T-SQL. Prüfe vorher sorgfältig, ob query_id und plan_id zur selben Abfrage gehören. Die folgenden IDs sind Platzhalter:

USE [DeineDatenbank];
GO

EXEC sys.sp_query_store_force_plan
    @query_id = 123,
    @plan_id = 456;

SQL Server versucht bei künftigen Ausführungen, den ausgewählten Plan zu verwenden. Das ist keine Garantie, dass jede Ausführung exakt denselben Plan erhält oder schneller wird. Insbesondere geänderte Indizes, andere Datenmengen oder unterschiedliche Parameterwerte können die Wirkung verändern. Prüfe daher nach dem Eingriff erneut Dauer, CPU-Zeit, Lesezugriffe und Fehlerbild unter einer repräsentativen Arbeitslast.

Die Planzwangsmaßnahme sollte einen konkreten Grund und einen Kontrolltermin haben. Sie verschafft dir Zeit, die eigentliche Ursache zu beheben: etwa veraltete Statistiken, eine ungünstige Abfrageformulierung, fehlende oder ungeeignete Indizes oder stark unterschiedliche Parameterwerte.

Erzwungene Pläne überwachen und wieder freigeben

Mit dieser Abfrage siehst du, welche Pläne in der Datenbank als erzwungen markiert sind und ob SQL Server dabei Fehler gemeldet hat:

USE [DeineDatenbank];
GO

SELECT
    query_id,
    plan_id,
    is_forced_plan,
    force_failure_count,
    last_force_failure_reason_desc
FROM sys.query_store_plan
WHERE is_forced_plan = 1;

Ein gesetztes is_forced_plan bedeutet, dass der Plan zum Erzwingen ausgewählt wurde. Prüfe zusätzlich force_failure_count und last_force_failure_reason_desc. Wenn die Erzwingung fehlschlägt, kann SQL Server wieder normal optimieren. Eine mögliche Ursache ist, dass ein für den alten Plan benötigter Index nicht mehr vorhanden ist.

Ist die Ursache behoben und die Abfrage ohne Zwang stabil, kannst du die Erzwingung aufheben:

USE [DeineDatenbank];
GO

EXEC sys.sp_query_store_unforce_plan
    @query_id = 123,
    @plan_id = 456;

Beobachte die Abfrage auch danach. Ein guter Plan für die Datenverteilung von heute muss nicht dauerhaft die beste Wahl bleiben.

Wenn Query Store keine passende Antwort liefert

Es fehlen aktuelle Abfragen

Prüfe zuerst actual_state_desc. Im Zustand READ_ONLY werden keine neuen Daten gesammelt. Kontrolliere außerdem Speichergrenze, Aufbewahrung und den eingestellten Erfassungsmodus. Bei sehr vielen einmaligen Ad-hoc-Abfragen kann Query Store schnell wachsen; eine geeignete Parametrisierung und ein passender Erfassungsmodus helfen, die Datenmenge zu begrenzen.

Es gibt keinen älteren guten Plan

Query Store kann nur Pläne vergleichen, die während seiner aktiven Erfassung gespeichert wurden. Fehlt ein früherer Plan, musst du die Abfrage mit den verfügbaren Informationen untersuchen. Dazu gehören der aktuelle Ausführungsplan, Statistiken, Indexe und die tatsächlich verwendeten Parameter. Erzwinge keinen Plan allein deshalb, weil er älter ist.

Mehrere Pläne sind ähnlich langsam

Dann liegt die Ursache möglicherweise nicht in einem einzelnen Planwechsel. Prüfe, ob die Abfrage inzwischen mehr Zeilen verarbeitet, ein Index fehlt, die Statistiken die Datenverteilung schlecht abbilden oder Wartezeiten außerhalb der Abfrage den Ablauf bremsen. Der Query Store hilft dir, den Zeitraum und die betroffenen Abfragen einzugrenzen; die Ursachenanalyse geht danach weiter.

Fazit

Mit dem SQL Server Query Store findest du Abfragen, deren Ressourcenverbrauch über einen längeren Zeitraum auffällt, und erkennst Änderungen bei der Planwahl. Besonders hilfreich ist der Vergleich einer Abfrage vor und nach einem Leistungseinbruch: Erst wenn Zeitraum, Ausführungsanzahl, Messwerte und Pläne zusammenpassen, lässt sich eine Planregression überzeugend begründen.

Einen früheren Plan zu erzwingen kann die Leistung kurzfristig stabilisieren. Dokumentiere den Eingriff, prüfe seine Wirkung und suche anschließend nach der eigentlichen Ursache. So bleibt aus einer schnellen Korrektur keine unbemerkte Dauereinstellung.

Weiterführende Dokumentation