Wissen · Query Store

SQL Server Query Store einrichten und im Betrieb prüfen

Wenn die Anwendung morgen langsam wird: Welche Spuren hätten Sie dann? Query Store hält Abfragetexte, Pläne und Laufzeiten direkt in der Datenbank fest. Ob er Ihnen im Ernstfall hilft, entscheidet sich aber vorher: an der Version, am Capture-Modus, am Speicher und daran, ob er tatsächlich schreibt. Im Lab auf SQL Server 2017 bis 2025 haben wir gesehen, wie leicht die Spuren lückenhaft werden. Im Modus AUTO zählt eine schnelle Abfrage ab SQL Server 2019 erst ab ihrer 30. Ausführung mit. Ein voller Speicher stoppt die Erfassung, ohne dass die Anwendung einen Fehler sieht. Und in einem bewusst extremen Grenzfall hat die Bereinigung auf drei von vier Versionen die gesamte Historie geleert.

Stand 07.11.2026 · ca. 22 Minuten

Lab SQL Server 2017–2025SQL-Abfragen 2Thema Zustand · Capture · Speicher

Kurz gesagt

Prüfen Sie zuerst den tatsächlichen Zustand, nicht die Einstellung, die Sie erwarten: Soll und Ist, Capture-Modus, belegten und maximalen Speicher, Cleanup und die Historie, die wirklich vorhanden ist. Abfrage 1 liefert das in einer Zeile. Legen Sie dann fest, welche Fragen Query Store später beantworten soll: welche Abläufe, welcher Zeitraum, welche Abfragen. Daraus folgen Capture-Modus, Speichergrenze und Aufbewahrung. Ändern Sie die Einstellungen gezielt und prüfen Sie danach, ob die erwarteten Abfragen tatsächlich ankommen. Ein voller Speicher und ein schreibgeschützter Zustand fallen nur auf, wenn jemand nachsieht.

Was Query Store festhält und was nicht

Query Store arbeitet je Datenbank und speichert in der Datenbank selbst. Er hält fest:

  • den Abfragetext, so wie SQL Server ihn kompiliert, also bei automatisch parametrisierten Abfragen bereits umgeschrieben,
  • die Pläne, die für diese Abfrage verwendet wurden, als Plan der Kompilierung im XML-Format, ohne tatsächliche Zeilenzahlen,
  • Laufzeitwerte je Zeitintervall: Zahl der Ausführungen, Dauer, CPU, logische und physische Reads, Speicherzuteilung, jeweils als Durchschnitt, Minimum, Maximum, letzter Wert und Standardabweichung,
  • Wartezeiten nach Kategorien wie Sperren, CPU oder Buffer I/O, ab SQL Server 2017.

Damit lässt sich beantworten, ob eine Abfrage seit einem Zeitpunkt einen anderen Plan nutzt, ob sie je Ausführung mehr Arbeit macht oder ob sie einfach häufiger läuft. Dafür ist Query Store gebaut, und dafür ist er unsere wichtigste Quelle für die Laufzeitgeschichte einer Datenbank. Auch festgelegte Pläne und Query Store Hints verwaltet SQL Server dort.

Welche Spuren nicht enthalten sind

  • Keine einzelnen Ausführungen. Die Werte sind je Intervall und Plan zusammengefasst. Welche Parameterwerte eine bestimmte langsame Ausführung hatte, steht nicht darin. Der gespeicherte Plan enthält nur die Parameterwerte, mit denen kompiliert wurde.
  • Keine Wartekette. Die Kategorie „Lock“ sagt, dass eine Abfrage auf Sperren gewartet hat, nicht auf wen. Dafür gibt es die Blocking-Diagnose und für Deadlocks den Deadlockgraph.
  • Keine DDL-Pläne. Ein TRUNCATE erschien in einem früheren PSG-Lab ohne eigenen Eintrag; seine Wartezeit landete beim aufrufenden Statement. Einzelheiten im Beitrag Wo steckt die Zeit?.
  • Nichts, was vor der Erfassung lief. Wer Query Store heute einschaltet, hat die Historie ab heute. Im Modus AUTO fehlt zusätzlich der Anfang jeder schnellen Abfrage, wie das Lab unten zeigt.

Den tatsächlichen Zustand Ihrer Datenbank prüfen

Abfrage 1 liest die Einstellungen und den Zustand der aktuellen Datenbank und ergänzt, wie weit die vorhandene Historie tatsächlich zurückreicht. Führen Sie sie in jeder Datenbank aus, die Sie prüfen wollen. Die Spalte befund fasst zusammen, worauf Sie zuerst schauen sollten.

Abfrage 1: Zustand, Erfassung und vorhandene Historie · nur lesend · geprüft auf SQL Server 2017–2025
/* PSG · Query Store · Abfrage 1: Zustand, Erfassung und tatsächlich vorhandene Historie   In der Datenbank ausführen, die Sie prüfen wollen (Query Store gilt je Datenbank).   Liest nur Katalogsichten. Ändert keine Einstellung und löscht nichts.   Ab SQL Server 2017. Rechte: VIEW DATABASE STATE, ab SQL Server 2022 genügt VIEW DATABASE PERFORMANCE STATE.   OPTION (MAXDOP 1): Die Diagnose soll keine parallelen Worker belegen, auch nicht auf einem ausgelasteten Server. */SELECT    DB_NAME()                                        AS datenbank,    CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(20)) AS version,    o.desired_state_desc                             AS soll,    o.actual_state_desc                              AS ist,    o.readonly_reason,    (SELECT STRING_AGG(r.grund, N'; ')     FROM (VALUES (1, N'Datenbank schreibgeschützt'), (2, N'Single-User-Modus'), (4, N'Emergency-Modus'),                  (8, N'Secondary Replica'), (65536, N'MAX_STORAGE_SIZE_MB erreicht'),                  (131072, N'interne Speichergrenze: Anzahl Statements'),                  (262144, N'interne Speichergrenze: noch nicht geschriebene Daten (vorübergehend)'),                  (524288, N'Datenträgergrenze der Datenbank erreicht')) AS r (bit, grund)     WHERE o.readonly_reason & r.bit = r.bit)          AS grund,    o.query_capture_mode_desc                        AS capture,    o.current_storage_size_mb                        AS belegt_mb,    o.max_storage_size_mb                            AS max_mb,    CAST(100.0 * o.current_storage_size_mb / NULLIF(o.max_storage_size_mb, 0) AS decimal(5, 1)) AS belegt_prozent,    o.size_based_cleanup_mode_desc                   AS cleanup_nach_groesse,    o.stale_query_threshold_days                     AS aufbewahrung_tage,    h.aeltestes_intervall,    CAST(DATEDIFF(hour, h.aeltestes_intervall, SYSDATETIMEOFFSET()) / 24.0 AS decimal(6, 1)) AS historie_tage,    o.interval_length_minutes                        AS intervall_min,    o.flush_interval_seconds                         AS flush_s,    o.wait_stats_capture_mode_desc                   AS waits,    (SELECT COUNT(*) FROM sys.query_store_query)     AS abfragen,    (SELECT COUNT(*) FROM sys.query_store_plan)      AS plaene,    CASE        WHEN o.actual_state_desc = N'ERROR' THEN N'Fehlerzustand: Query Store schreibt nicht.'        WHEN o.actual_state_desc = N'OFF' THEN N'Query Store ist aus: Es entsteht keine Historie.'        WHEN o.desired_state_desc <> o.actual_state_desc THEN N'Soll und Ist weichen ab: Spalte grund lesen.'        WHEN o.actual_state_desc = N'READ_ONLY' THEN N'Bewusst nur lesend: Es kommen keine Daten hinzu.'        WHEN o.query_capture_mode_desc = N'NONE' THEN N'Neue Abfragen werden nicht erfasst.'        WHEN o.current_storage_size_mb >= 0.9 * o.max_storage_size_mb AND o.size_based_cleanup_mode_desc = N'OFF'            THEN N'Speicher fast voll und kein Cleanup nach Größe: READ_ONLY droht.'        WHEN o.current_storage_size_mb >= 0.9 * o.max_storage_size_mb            THEN N'Speicher an der Grenze: Cleanup löscht Historie.'        ELSE N'Erfasst. Reicht die Historie für Ihre Fragen?'    END                                              AS befundFROM sys.database_query_store_options AS oCROSS JOIN (    SELECT MIN(i.start_time) AS aeltestes_intervall    FROM sys.query_store_runtime_stats_interval AS i    WHERE EXISTS (SELECT 1 FROM sys.query_store_runtime_stats AS rs                  WHERE rs.runtime_stats_interval_id = i.runtime_stats_interval_id)) AS hOPTION (MAXDOP 1);

Im Lab lieferte Abfrage 1 für eine Datenbank, deren Speichergrenze überschritten war, auf SQL Server 2022 diese Zeile, hier untereinander dargestellt und um einige Spalten gekürzt:

Beispielausgabe
datenbank             QsFull
version               16.0.4295.3
soll                  READ_WRITE
ist                   READ_ONLY
readonly_reason       65536
grund                 MAX_STORAGE_SIZE_MB erreicht
capture               ALL
belegt_mb             31
max_mb                10
belegt_prozent        310.0
cleanup_nach_groesse  OFF
aufbewahrung_tage     30
historie_tage         .0
abfragen              6111
befund                Soll und Ist weichen ab: Spalte grund lesen.

Die Grenze von 10 MB ist ein Laborwert, damit der Fall in Minuten eintritt. Abfrage 1 lief auf SQL Server 2017, 2019, 2022 und 2025 ohne Fehler, auch bei ausgeschaltetem Query Store und leerer Historie.

So lesen Sie die wichtigsten Spalten:

  • soll und ist sind zwei verschiedene Angaben. soll ist, was eingestellt wurde, ist, was Query Store gerade tut. Weichen sie ab, nennt grund die Ursache. Mehr dazu unten im Abschnitt Warum Query Store READ_ONLY ist.
  • belegt_mb und max_mb zeigen, wie nah die Speichergrenze ist. Der belegte Wert kann eine Zeit lang hinter der Wirklichkeit herlaufen, weil Query Store die Grenze nur beim Schreiben auf die Platte prüft. Im Lab stand er so beim Dreifachen der Grenze.
  • aufbewahrung_tage und historie_tage: Die erste Zahl ist die eingestellte Aufbewahrung, die zweite reicht so weit zurück, wie tatsächlich Laufzeitdaten vorhanden sind. Ist historie_tage deutlich kleiner, obwohl Query Store länger läuft, hat der Cleanup nach Größe schon gelöscht, Query Store war zwischendurch aus oder schreibgeschützt, jemand hat ihn geleert, oder er läuft schlicht noch nicht so lange.
  • capture entscheidet, welche Abfragen überhaupt hineinkommen. Der nächste Abschnitt zeigt, was das praktisch bedeutet.

Unter ALL landet übrigens auch Abfrage 1 selbst im Query Store der Datenbank, in der Sie sie ausführen. Das ist harmlos, aber wundern Sie sich nicht, sie später dort zu finden.

Version und Ausgangszustand unterscheiden

Welche Einstellungen eine Datenbank hat, hängt davon ab, wie sie entstanden ist. Im Lab haben wir je Version eine neue Datenbank angelegt, model gelesen und eine auf SQL Server 2019 erstellte Datenbank auf 2022 und 2025 wiederhergestellt:

SQL Server 2017SQL Server 2019SQL Server 2022SQL Server 2025
Neue DatenbankOFFOFFREAD_WRITEREAD_WRITE
Capture-ModusALLAUTOAUTOAUTO
Speichergrenze100 MB1.000 MB1.000 MB1.000 MB
Query Store in modelausausREAD_WRITEREAD_WRITE
Datenbank aus 2019 wiederhergestellt––OFFOFF

Auf allen vier Versionen gleich: Intervall 60 Minuten, Schreiben auf die Platte alle 900 Sekunden, Aufbewahrung 30 Tage, Cleanup nach Größe eingeschaltet, Wartezeiten eingeschaltet.

Zwei Dinge fallen auf. Erstens erbt eine neue Datenbank den Query-Store-Zustand von model; ab SQL Server 2022 steht model auf READ_WRITE. Zweitens kommt eine wiederhergestellte Datenbank mit ihrem eigenen Zustand an. Die Datenbank aus SQL Server 2019 blieb auf 2022 und 2025 bei OFF, obwohl dort neue Datenbanken Query Store eingeschaltet hätten. Auch ihr Kompatibilitätsgrad blieb bei 150. Nach einem Upgrade per Backup und Restore hat Ihre wichtigste Datenbank also womöglich keinen Query Store, während alle neuen Testdatenbanken einen haben.

Gerade vor einem Upgrade ist das der falsche Zustand. Für den Vergleich von vorher und nachher muss Query Store schon auf der alten Version mitgeschrieben haben. Den ganzen Ablauf beschreibt der Beitrag SQL-Server-Upgrade: schneller oder langsamer?.

Capture-Modus und Diagnoseziel zusammenbringen

Der Capture-Modus legt fest, welche Abfragen Query Store neu aufnimmt. Was erfasst sein muss, ergibt sich aus Ihren späteren Fragen: Wollen Sie wissen, welche Abfragen teuer waren, reicht etwas anderes als bei der Frage, welche Abfragen eine Anwendung überhaupt ausführt.

ALL, AUTO, CUSTOM und NONE im Lab

Wir haben auf jeder Version dieselbe Arbeitsfolge in getrennten Datenbanken ausgeführt, je eine pro Modus:

  • eine häufige, schnelle Abfrage 50-mal,
  • dieselbe Art Abfrage genau 1-, 5-, 10-, 29-, 30-, 31- und 60-mal, jeweils mit eigenem Text,
  • 100 verschiedene schnelle Abfragen je einmal,
  • eine teure Abfrage einmal, rund eine Sekunde CPU.

Danach haben wir gezählt, wie viele dieser Ausführungen Query Store kennt. SQL Server 2019 und 2022 lieferten identische Zahlen:

Abfrage, tatsächlich ausgeführtALLAUTOCUSTOM mit EXECUTION_COUNT = 5
häufig, 50-mal502146
1-mal1––
5-mal5–1
29-mal29–25
30-mal30126
60-mal603156
100 verschiedene, je 1-mal100 Abfragen––
teuer, 1-mal111

Die Zahlen sind die Ausführungen, die Query Store danach kannte. Ein Strich heißt: Die Abfrage steht nicht im Query Store.

Capture-Rechner: Wie viele Ausführungen einer Abfrage kennt Query Store je Modus? Regeln aus dem PSG-Lab, an den gemessenen Stützpunkten exakt.

ALL erfasst jede Abfrage ab der ersten Ausführung. Das ist der Default auf SQL Server 2017.

AUTO, der Default ab SQL Server 2019, hat die CPU-intensive Abfrage sofort erfasst. Bei den schnellen Abfragen begann die Zählung erst mit der 30. Ausführung: Von 50 Ausführungen kannte Query Store 21, von 60 kannte er 31, von 29 keine. Die ersten 29 Ausführungen jeder schnellen Abfrage fehlen dauerhaft; sie werden auch nicht nachgetragen, wenn die Abfrage später die Schwelle erreicht. Die 100 seltenen Abfragen erschienen gar nicht. Das passt zu den Schwellen, die Microsoft für den Modus CUSTOM als Standard nennt: 30 Ausführungen, eine Sekunde Kompilier-CPU oder 100 ms Ausführungs-CPU, jeweils zusammengezählt innerhalb eines Tages. Für AUTO selbst bezeichnet Microsoft die Schwellen als intern festgelegt, und SQL Server 2017 zeigt, dass sie sich ändern können: Dort erfasste AUTO schon ab der dritten Ausführung, von 50 Ausführungen also 48. Einmal ausgeführte Abfragen fehlten auch dort.

CUSTOM (ab SQL Server 2019) erlaubt eigene Schwellen. Mit EXECUTION_COUNT = 5 zählte Query Store ab der fünften Ausführung mit.

NONE nimmt keine neuen Abfragen mehr auf, zählt aber für bekannte weiter. Im Lab stieg die häufige Abfrage nach dem Umschalten von 50 auf 100 Ausführungen, eine neue Abfrage erschien nicht. NONE ist also nicht dasselbe wie ausgeschaltet. Microsoft rät, NONE nur für einen konkreten Zweck zu verwenden.

Was folgt daraus? AUTO hält den Bestand klein und verliert keine CPU-intensive Abfrage. Weil die Schwellen an CPU und Ausführungszahl hängen, kann eine selten laufende Abfrage, die lange wartet, aber kaum CPU braucht, darunter bleiben; das haben wir nicht eigens gemessen. Für die Fragen „Wie oft läuft diese Abfrage wirklich?“ oder „Welche Abfragen erzeugt die Anwendung?“ liefert AUTO aber zu kleine Zahlen und eine unvollständige Liste. Wer Ausführungszahlen vergleichen will, sollte das wissen, bevor er aus einem Query-Store-Bericht eine Häufigkeit abliest. ALL liefert vollständige Zahlen, belegt aber bei Anwendungen mit vielen unterschiedlichen Abfragetexten erwartungsgemäß mehr Speicher. Eine Standardempfehlung für jede Produktion gibt es nicht.

Intervall, Flush und Wartezeiten

  • Intervall: Laufzeitwerte werden je Intervall zusammengefasst, standardmäßig 60 Minuten. Zulässig sind 1, 5, 10, 15, 30, 60 und 1440 Minuten. Kürzere Intervalle zeigen Spitzen genauer, erzeugen aber mehr Zeilen.
  • Flush: Query Store sammelt zunächst im Arbeitsspeicher und schreibt standardmäßig alle 900 Sekunden auf die Platte. Erst dann wird auch die Speichergrenze geprüft. Mit sys.sp_query_store_flush_db lässt sich das Schreiben vorziehen. Was bei einem Absturz noch nicht geschrieben war, fehlt danach.
  • Wartezeiten: ab SQL Server 2017 standardmäßig eingeschaltet. Sie sind nach Kategorien zusammengefasst und zeigen keine Wartekette.

Speicher, Aufbewahrung und Cleanup planen

Query Store begrenzt seinen Platz mit MAX_STORAGE_SIZE_MB: 100 MB bis SQL Server 2017, 1.000 MB ab 2019. Zwei Mechanismen halten den Bestand unter dieser Grenze:

  • Aufbewahrung (STALE_QUERY_THRESHOLD_DAYS, Standard 30 Tage) entfernt Daten, die älter sind.
  • Cleanup nach Größe (SIZE_BASED_CLEANUP_MODE = AUTO, Standard) beginnt laut Microsoft bei etwa 90 % der Grenze und löscht bis etwa 80 %, zuerst die billigsten und ältesten Abfragen.

Wie viel Platz nötig ist, hängt nach unserer Erfahrung vor allem an der Zahl unterschiedlicher Abfragetexte und Pläne, dazu an Intervalllänge und Aufbewahrung, kaum an der Datenbankgröße. Im Lab belegten rund 6.100 verschiedene, je einmal ausgeführte Abfragen mit Join auf SQL Server 2022 und 2025 etwa 30 MB, rund 5 KB je Abfrage mit Plan und einer Laufzeitzeile. Häufig ausgeführte Abfragen erzeugen je Intervall weitere Zeilen. Eine Anwendung, die Werte direkt in den SQL-Text schreibt statt Parameter zu verwenden, erzeugt für jeden neuen Wert eine neue Abfrage und füllt Query Store entsprechend schnell.

Was passiert, wenn die Grenze zu knapp ist

Um das Verhalten an der Grenze zu sehen, haben wir die Grenze im Lab bewusst auf 10 MB gesetzt und nach der Arbeitsfolge 6.000 verschiedene Abfragen ausgeführt, einmal ohne und einmal mit Cleanup nach Größe. 10 MB ist ein Laborwert, keine Größe für die Praxis.

Ohne Cleanup ging Query Store auf allen vier Versionen auf READ_ONLY. SQL Server 2022 und 2025 hatten zu diesem Zeitpunkt alle 6.000 Abfragen erfasst und standen bei 31 und 30 MB, gut dem Dreifachen der Grenze: Die Größe wird erst beim Schreiben auf die Platte geprüft. SQL Server 2017 und 2019 stoppten früher, bei rund 3.500 statt 6.000 Abfragen und einem angezeigten Bestand von 17 bis 20 MB, mit dem Grund 262144, also an der internen Grenze für noch nicht geschriebene Daten. Dort fehlten am Ende 15 bis 17 % der Abfragen ganz.

Mit Cleanup passierte zunächst dasselbe: READ_ONLY bei rund 30 MB. Dann begann der Cleanup zu löschen, zwischen zwei unserer Messpunkte im Abstand von 15 Sekunden jeweils einige Hundert bis knapp 2.000 Abfragen:

SQL Server 2017SQL Server 2019SQL Server 2022SQL Server 2025
Abfragen zu Beginn des Cleanups3.3524.4136.1106.100
übrig nach rund 2 Minuten (ab Beginn des Cleanups)279, kurz danach 0847, Cleanup lief noch0 nach 76 s0 nach 93 s
erhalten gebliebennichtsdie häufige, die teure, die 60-mal ausgeführte und rund 850 Füllabfragennichtsnichts
Messreihen aus dem Lab: Abfragen im Query Store und belegter Speicher nach dem Füllen, je Version ohne und mit Cleanup nach Größe.

Auf drei von vier Versionen war am Ende keine einzige Abfrage mehr übrig, auch nicht die häufige und die teure. Erst dann stand Query Store wieder auf READ_WRITE. Statt bei 80 % aufzuhören, war die Historie leer. Auf SQL Server 2019 lief der Cleanup langsamer und hatte bis zum Ende unserer Beobachtung die billigen Abfragen zuerst entfernt, wie Microsoft es beschreibt. Auffällig war auf allen Versionen, dass belegt_mb während des Löschens viel langsamer sank als die Zahl der Abfragen. Unsere Vermutung, nicht weiter geprüft: Der Cleanup orientiert sich an diesem Wert und erkennt deshalb nicht rechtzeitig, dass er sein Ziel erreicht hat.

Das ist ein Grenzfall mit einer winzigen Grenze und dreifacher Überschreitung. Er zeigt aber, worauf es ankommt: Die Grenze muss zur Zahl unterschiedlicher Abfragen passen, und sie sollte so bemessen sein, dass der Cleanup die Ausnahme bleibt. Und historie_tage in Abfrage 1 ist die Zahl, die zeigt, ob Ihre Historie tatsächlich so weit zurückreicht wie geplant. In einem früheren PSG-Lab unter Last dauerte ein Cleanup nach Größe 30 bis 55 Sekunden, und die Antwortzeiten der Anwendung lagen in dieser Zeit höher, unter einer Availability Group deutlicher als auf einem Einzelserver. Das waren einzelne Läufe; die Ursache im Detail haben wir dort nicht isoliert. Ein Cleanup ist also Arbeit, die Sie lieber selten haben.

Die Aufbewahrung beginnt bei Ihren Fragen

Wenn ein Problem nur beim Monatsabschluss auftritt, muss der letzte Monatsabschluss in der Historie liegen, besser die letzten zwei. Wenn Sie nach einem Release vergleichen wollen, muss die Zeit davor noch vorhanden sein. Eine eingestellte Zahl von Tagen garantiert das nicht, denn der Cleanup nach Größe kann früher löschen. Prüfen Sie deshalb nach einem vollen Geschäftszyklus, ob historie_tage die eingestellte Aufbewahrung erreicht.

Warum Query Store READ_ONLY ist

„Query Store soll schreiben“ und „Query Store schreibt“ sind zwei Angaben. Abfrage 1 zeigt beide nebeneinander. Steht ist auf READ_ONLY, obwohl soll READ_WRITE lautet, nennt readonly_reason den Grund. Der Wert ist eine Bitmaske; mehrere Gründe können gleichzeitig gelten. Abfrage 1 zerlegt ihn in der Spalte grund:

WertBedeutung laut Microsoft
1Datenbank ist schreibgeschützt
2Datenbank ist im Single-User-Modus
4Datenbank ist im Emergency-Modus
8Datenbank ist eine Secondary Replica
65536Query Store hat MAX_STORAGE_SIZE_MB erreicht
131072Zahl unterschiedlicher Statements hat die interne Speichergrenze erreicht
262144Noch nicht geschriebene Daten haben die interne Speichergrenze erreicht; vorübergehend
524288Datenbank hat ihre Größengrenze auf dem Datenträger erreicht
readonly_reason zerlegen: Wert eingeben oder Bits anklicken. Texte laut Microsoft, Handlungen aus diesem Beitrag.

Im Lab haben wir drei dieser Gründe gesehen:

  • Datenbank schreibgeschützt (1): Nach ALTER DATABASE … SET READ_ONLY stand Query Store auf READ_ONLY mit Grund 1 und kehrte nach SET READ_WRITE innerhalb weniger Sekunden von selbst zurück.
  • Speichergrenze erreicht (65536): Nach dem Anheben von MAX_STORAGE_SIZE_MB von 10 auf 40 MB kehrte Query Store auf allen vier Versionen von selbst zu READ_WRITE zurück. Ein zusätzliches OPERATION_MODE = READ_WRITE war nicht nötig. Im Lab dauerte das 16 bis 61 Sekunden, weil wir alle 60 Sekunden schreiben ließen. Mit dem Standardintervall von 900 Sekunden kann es bis zu einer Viertelstunde dauern, bis Abfrage 1 wieder READ_WRITE zeigt.
  • Interne Speichergrenze (262144): Auf SQL Server 2017 und 2019 meldete Query Store beim Füllen diesen Grund statt 65536, als viele neue Abfragen auf einmal kamen, und nahm ab da keine neuen mehr auf. Microsoft beschreibt den Zustand als vorübergehend. Im Lauf ohne Cleanup blieb er bis zum Anheben der Grenze stehen, im Lauf mit Cleanup ging er in 65536 über.

Neben READ_ONLY kann ist auch ERROR lauten. Dann schreibt Query Store ebenfalls nicht, und readonly_reason hilft nicht weiter. Abfrage 1 meldet diesen Fall eigens; wie man ihn behebt, beschreibt Microsoft in den Best Practices zum Query Store. Im Lab haben wir ERROR nicht erzeugt.

Wichtig ist, was in der Zwischenzeit geschieht: In der schreibgeschützten Phase haben wir die häufige Abfrage 20-mal ausgeführt und eine neue Abfrage 20-mal. Query Store kannte danach weder die 20 zusätzlichen Ausführungen noch die neue Abfrage. Nach der Rückkehr zu READ_WRITE zählte er wieder, die Lücke blieb. Die Anwendung erhält dabei keinen Fehler. Ohne regelmäßigen Blick auf Abfrage 1 fällt so eine Lücke erst auf, wenn man die Daten braucht.

Daraus folgt die Reihenfolge:

  1. Zustand mit Abfrage 1 sichern, bevor Sie etwas ändern.
  2. Den Grund lesen. Mehr Speicher passt zu 65536, nicht zu einer schreibgeschützten Datenbank oder einer Secondary Replica.
  3. Die passende, begrenzte Änderung vornehmen.
  4. Abfrage 1 nach dem nächsten Schreibvorgang wiederholen: Steht ist wieder auf READ_WRITE?
  5. Mit Abfrage 2 prüfen, ob eine bekannte Abfrage wieder neue Ausführungen erhält.

ALTER DATABASE … SET QUERY_STORE CLEAR ist keine Reparatur. Es löscht die gesamte Historie, und keine Einstellung holt sie zurück. Mit ihr gehen auch die Planfestlegungen verloren, die an den gespeicherten Plänen hängen.

Warum eine erwartete Abfrage fehlt

Sie suchen eine Abfrage im Query Store und finden sie nicht. Prüfen Sie in dieser Reihenfolge:

  1. Datenbank und Zeitraum. Maßgeblich ist der Datenbankkontext, in dem die Anweisung läuft. Im Lab fehlte eine Ad-hoc-Abfrage, die aus master heraus auf die Zieldatenbank zugriff, in deren Query Store. Eine Prozedur der Zieldatenbank, aus master aufgerufen, erschien dagegen dort. In master und tempdb lässt sich Query Store nicht einschalten.
  2. Zustand. War Query Store zum fraglichen Zeitpunkt aus oder schreibgeschützt? historie_tage und ist aus Abfrage 1 geben den ersten Hinweis.
  3. Capture-Modus. Unter AUTO erscheinen schnelle, seltene Abfragen nicht, und ab SQL Server 2019 fehlen die ersten 29 Ausführungen.
  4. Aufbewahrung und Cleanup. Ältere oder billige Abfragen können bereits gelöscht sein.
  5. Text und Kontext. Dieselbe Anweisung kann unter mehreren Einträgen liegen, oder ihr Text sieht anders aus als im Code.

Den fünften Punkt prüft Abfrage 2. Sie sucht einen Textausschnitt und zeigt je Treffer, ob es eine Ad-hoc-Abfrage oder ein Teil einer Prozedur ist, unter welchem SET-Kontext sie lief und wie oft. Die Textsuche liest alle gespeicherten Abfragetexte; bei einem Query Store im Gigabytebereich dauert das. Führen Sie sie gezielt aus, nicht im Takt eines Überwachungsjobs.

Abfrage 2: Wo ist meine Abfrage? · nur lesend · geprüft auf SQL Server 2017–2025
/* PSG · Query Store · Abfrage 2: Wo ist meine Abfrage?   Sucht einen Textausschnitt in den erfassten Abfragetexten der aktuellen Datenbank und zeigt,   unter welchen Einträgen (ad hoc, Prozedur, SET-Kontext) dieselbe Anweisung liegt.   Liest nur Katalogsichten. Ändert nichts. Ab SQL Server 2017; Rechte wie Abfrage 1.   Achtung: Abfragetexte können vertrauliche Literale und Namen enthalten. Ergebnis nicht ungeprüft weitergeben.   OPTION (MAXDOP 1): Die Diagnose soll keine parallelen Worker belegen. */DECLARE @suchtext nvarchar(400) = N'%Orders%';  -- Ausschnitt aus dem Abfragetext, mit % davor und danach SELECT TOP (50)    q.query_id,    CASE WHEN q.object_id = 0 THEN N'(ad hoc)'         ELSE ISNULL(OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id),                     N'(Objekt ' + CAST(q.object_id AS nvarchar(20)) + N', Name nicht sichtbar)') END AS objekt,    q.context_settings_id                                    AS kontext,    CASE WHEN CAST(cs.set_options AS bigint) & 4096 = 4096 THEN N'ON' ELSE N'OFF' END AS arithabort,    s.plaene,    s.ausfuehrungen,    s.erste_ausfuehrung,    s.letzte_ausfuehrung,    REPLACE(REPLACE(LEFT(qt.query_sql_text, 150), CHAR(13), N' '), CHAR(10), N' ') AS abfragetextFROM sys.query_store_query_text AS qtJOIN sys.query_store_query AS q ON q.query_text_id = qt.query_text_idJOIN sys.query_context_settings AS cs ON cs.context_settings_id = q.context_settings_idOUTER APPLY (    SELECT COUNT(DISTINCT p.plan_id)    AS plaene,           SUM(rs.count_executions)     AS ausfuehrungen,           MIN(rs.first_execution_time) AS erste_ausfuehrung,           MAX(rs.last_execution_time)  AS letzte_ausfuehrung    FROM sys.query_store_plan AS p    LEFT JOIN sys.query_store_runtime_stats AS rs ON rs.plan_id = p.plan_id    WHERE p.query_id = q.query_id) AS sWHERE qt.query_sql_text LIKE @suchtextORDER BY s.letzte_ausfuehrung DESCOPTION (MAXDOP 1);

Im Lab haben wir dieselbe Summenabfrage auf vier Wegen ausgeführt: zweimal parametrisiert über sp_executesql, einmal mit ARITHABORT ON und einmal mit OFF, einmal in der Prozedur dbo.GetCustomerTotal und einmal als Ad-hoc-Text mit führendem Kommentar und dem festen Wert 7. Abfrage 2 fand vier Einträge:

Beispielausgabe
query_id  objekt                kontext  arithabort  ausfuehrungen  abfragetext
116       (ad hoc)              3        ON          1              (@1 tinyint)SELECT SUM([Amount]) [w_ctx2] FROM [dbo].[Orders] WHERE [CustomerId]=@1
115       dbo.GetCustomerTotal  3        ON          1              (@CustomerId int)SELECT SUM(Amount) AS w_ctx FROM dbo.Orders WHERE CustomerId = @CustomerId
114       (ad hoc)              1        OFF         1              (@CustomerId int)SELECT SUM(Amount) AS w_ctx FROM dbo.Orders WHERE CustomerId = @CustomerId
113       (ad hoc)              3        ON          1              (@CustomerId int)SELECT SUM(Amount) AS w_ctx FROM dbo.Orders WHERE CustomerId = @CustomerId

Gekürzt um die Spalten plaene, erste_ausfuehrung und letzte_ausfuehrung, SQL Server 2022 mit @suchtext = N'%w[_]ctx%'. Die übrigen Versionen lieferten dieselben vier Einträge.

  • Zwei Einträge für denselben Text: ARITHABORT ON und OFF ergeben verschiedene Kontexte und damit verschiedene query_id. SQL Server Management Studio setzt ARITHABORT ON, Anwendungen über ODBC oder .NET in der Regel nicht. Im Lab lief ein Prozeduraufruf über sqlcmd ohne eigene Einstellung mit OFF. Wer eine langsame Anwendungsabfrage in SSMS nachstellt, schaut deshalb womöglich auf den falschen Eintrag und den falschen Plan.
  • Ein eigener Eintrag für die Prozedur, erkennbar am Objektnamen.
  • Ein umgeschriebener Text: Die Ad-hoc-Abfrage hat SQL Server automatisch parametrisiert. Im Query Store steht sie als (@1 tinyint)SELECT SUM([Amount]) [w_ctx2] FROM [dbo].[Orders] WHERE [CustomerId]=@1, ohne den Kommentar, mit eckigen Klammern und ohne den Wert 7. Eine Suche nach CustomerId = 7 findet sie nicht. Suchen Sie deshalb nach einem Tabellen- oder Spaltennamen, nicht nach Literalen, Leerzeichen oder Kommentaren.

Rechte für die beiden Abfragen

Beide Abfragen lesen Query-Store-Sichten und brauchen dafür ein Leserecht auf die Datenbank. Wir haben beide mit einem eigenen Benutzer ohne weitere Rechte geprüft. Ein Benutzer ohne dieses Recht erhielt im Lab auf allen Versionen Meldung 262, im Original:

Beispielausgabe
Msg 262, Level 14, State 1, Server …, Line 6
VIEW DATABASE STATE permission denied in database 'QsALL'.

So lautete sie auf SQL Server 2017 und 2019. Auf 2022 und 2025 nennt dieselbe Meldung VIEW DATABASE PERFORMANCE STATE. VIEW DATABASE STATE genügte auf allen vier Versionen. Ab SQL Server 2022 reichte das engere VIEW DATABASE PERFORMANCE STATE allein. Für ein Überwachungskonto ist das die bessere Wahl, weil es nur die Leistungssichten öffnet.

Ohne das Recht, die Definition der Objekte zu sehen (VIEW DEFINITION), kennt der Benutzer die Namen der Prozeduren nicht. Abfrage 2 zeigt dann statt dbo.GetCustomerTotal den Eintrag (Objekt 965578478, Name nicht sichtbar). Die übrigen Spalten bleiben vollständig.

Einstellungen ändern und das Ergebnis prüfen

-- Ändert die Query-Store-Konfiguration der Datenbank. Werte sind Beispiele.ALTER DATABASE CURRENT SET QUERY_STORE = ON (    OPERATION_MODE = READ_WRITE,    QUERY_CAPTURE_MODE = AUTO,              -- ALL, wenn vollständige Ausführungszahlen gebraucht werden    MAX_STORAGE_SIZE_MB = 2048,             -- an der Zahl unterschiedlicher Abfragen ausrichten    SIZE_BASED_CLEANUP_MODE = AUTO,    CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 45),   -- mindestens ein voller Geschäftszyklus    INTERVAL_LENGTH_MINUTES = 60,    WAIT_STATS_CAPTURE_MODE = ON);

Die Anweisung braucht das Recht ALTER auf der Datenbank. Wir haben sie unverändert auf allen vier Versionen ausgeführt. Danach prüfen Sie in drei Schritten:

  1. Nach dem ersten Schreibvorgang (spätestens nach dem Flush-Intervall): Abfrage 1. Stimmen soll und ist überein?
  2. Nach einem typischen Arbeitstag: Abfrage 2 für zwei oder drei bekannte Abfragen. Kommen sie an, und passen die Ausführungszahlen grob zu dem, was Sie erwarten?
  3. Nach einem Geschäftszyklus: Abfrage 1 erneut. Reicht historie_tage so weit wie geplant, und wie weit ist belegt_prozent gestiegen?

Halten Sie zu jeder Änderung fest, was vorher galt, was Sie erwarten und bis wann Sie nachsehen. Der Rückweg ist einfach, solange es nur um Einstellungen geht. Gelöschte Historie lässt sich aber nicht zurückstellen.

Und der Aufwand? In einem früheren PSG-Lab mit 2.400 Operationen pro Sekunde, überwiegend kurzen Lesezugriffen mit wenigen verschiedenen Abfragen, lag der Median des 99. Perzentils der Antwortzeit aus 18 kurzen Läufen ohne Query Store bei 20,6 ms, mit Query Store bei 21,4 bis 21,6 ms. Das ist ein einzelner Messaufbau mit viel freier CPU, keine Zusage für jede Last. Anwendungen mit sehr vielen verschiedenen Abfragetexten haben wir dort nicht gemessen. Teurer als die Erfassung kann der Cleanup sein, siehe oben.

Abfragetexte und Pläne vertraulich behandeln

Query Store enthält die Abfragetexte Ihrer Anwendung, mit Tabellen- und Spaltennamen, Kommentaren und allen Werten, die direkt im Text stehen. Die gespeicherten Pläne enthalten zusätzlich die Parameterwerte, mit denen kompiliert wurde. Behandeln Sie Auszüge und Exporte deshalb wie Anwendungsdaten: nur an Berechtigte, nicht in öffentliche Foren, nicht ungeprüft in Tickets. Dass Query Store keine vollständige Parameterchronik führt, macht die vorhandenen Daten nicht unkritisch.

Von der Erfassung zur Auswertung

Dieser Beitrag endet dort, wo die eigentliche Diagnose beginnt. Wenn Query Store zuverlässig mitschreibt, helfen diese Beiträge weiter:

Für einen Rückblick über viele Datenbanken und Wochen wertet PSG QX auf Wunsch den Query Store Ihrer Datenbanken aus: welche Abfrage, welcher Plan, seit wann. QX zeichnet dabei nichts Neues auf. Es braucht einen Query Store, der vorher mitgeschrieben hat. Genau deshalb lohnt sich die Prüfung aus diesem Beitrag, bevor es eilt.

Häufige Fragen

Kann ich Query Store einfach einschalten? Technisch ja, mit einer Anweisung. Sinnvoll wird es, wenn Sie vorher den Istzustand lesen und festlegen, welche Historie Sie brauchen. Danach wählen Sie Capture-Modus, Speichergrenze und Aufbewahrung und prüfen, ob die erwarteten Abfragen ankommen. Eine Universalkonfiguration für jede Anwendung gibt es nicht.

Bremst Query Store meinen Server? In unserem Lastlab lag das 99. Perzentil mit Query Store um rund eine Millisekunde höher, bei wenigen verschiedenen Abfragen und viel freier CPU. Spürbarer kann der Cleanup nach Größe werden, wenn die Grenze zu knapp ist und viele Abfragen auf einmal gelöscht werden. Eine ausreichende Speichergrenze ist deshalb auch eine Frage der Leistung.

Warum ist meine Abfrage nicht im Query Store? Prüfen Sie Datenbank und Zeitraum, den tatsächlichen Zustand, den Capture-Modus, Aufbewahrung und Cleanup und zuletzt den Text. Unter AUTO erscheinen schnelle, seltene Abfragen nicht. Automatische Parametrisierung schreibt den Text um. Ein fehlender Eintrag beweist nicht, dass die Abfrage nie lief.

Löst mehr Speicher jeden READ_ONLY-Zustand? Nein. Mehr Speicher passt zum Grund 65536. Eine schreibgeschützte Datenbank, eine Secondary Replica oder eine interne Speichergrenze brauchen eine andere Antwort. Lesen Sie readonly_reason, bevor Sie etwas ändern.

Nimmt eine Datenbank ihren Query Store bei Backup und Restore mit? Laut Microsoft ja, denn Query Store liegt in der Datenbank. Im Lab haben wir den Zustand geprüft: Eine Datenbank aus SQL Server 2019 kam auf 2022 und 2025 mit OFF an, obwohl neue Datenbanken dort Query Store eingeschaltet haben. Prüfen Sie nach jedem Umzug mit Abfrage 1.

Sind Query-Store-Daten anonym? Nein. Abfragetexte und Pläne können Tabellennamen, Werte und Kompilierungsparameter enthalten. Behandeln Sie Zugriff und Export wie bei Anwendungsdaten.

Wenn die Historie fehlt, wenn Sie sie brauchen

Oft fällt erst im Problemfall auf, dass Query Store aus war, seit Tagen nur noch lesend lief oder der Cleanup die entscheidende Woche schon gelöscht hat. Für eine gemeinsame Untersuchung helfen:

  • die Ausgabe von Abfrage 1 aus den betroffenen Datenbanken,
  • der Zeitraum, um den es geht, und was in dieser Zeit anders war: Release, Upgrade, Monatsabschluss,
  • zwei oder drei Abfragen, die Sie im Query Store erwarten, mit der Ausgabe von Abfrage 2.

Mit diesen Angaben sehen wir schnell, ob die Historie für die Frage reicht, und wenn nicht, was bis zum nächsten Auftreten erfasst sein muss.

Quellen

Weiterlesen: Mal schnell, mal langsam · Wo steckt die Zeit? · SQL-Server-Upgrade: schneller oder langsamer? · SQL Server Performance Optimierung