Ratgeber · SQL Server Performance

Warum hilft ein Neustart des SQL Servers, und warum nur kurz?

Monatsende. Abrechnung und Revision laufen gleichzeitig, die Anwendung reagiert kaum noch, der Datenbankserver meldet über 90 Prozent CPU-Auslastung. Jemand startet den Server neu, eine Stunde später ist alles wieder flott. Zwei Wochen danach beginnt es von vorn. Die Geschäftsleitung will neue Hardware, der Hardwarelieferant findet nichts, der Softwarehersteller sieht das Problem nicht bei sich, und in der Buchhaltung ist von Erdstrahlen die Rede.

Stand 21.10.2026 · ca. 13 Minuten

Abfrage LagebildGeprüft SQL Server 2017–2025Lab Neustart · Sniffing
Neustart, dann zuerst der kleine KundeGroßkunde bekommt Seek + Key Lookup1.777 msmit passendem Plan: 302 msNeustart, dann zuerst der Großkundekleiner Kunde bekommt den Scan109,8 msmit passendem Plan: 0,07 msGleicher Neustart, andere Reihenfolge:Mal leidet der Großkunde, mal der kleine.

Der Neustart hat tatsächlich geholfen. Er beweist aber nicht, warum. Ein Neustart setzt viele Dinge gleichzeitig zurück, und welches davon den Unterschied gemacht hat, verrät er nicht. Zugleich löscht er genau die Spuren, mit denen sich die Ursache hätte finden lassen.

Diese Seite ist Teil unseres Ratgebers SQL Server Performance. Sie erklärt, was ein Neustart zurücksetzt, welche Erklärungen für die Besserung in Frage kommen und welche Belege Sie in fünf Minuten sichern, bevor Sie neu starten.

Was ein Neustart alles zurücksetzt

Gemeint ist hier der Neustart des SQL-Server-Dienstes. Ein Neustart des ganzen Servers setzt zusätzlich das Betriebssystem und alle anderen Programme auf dem Rechner zurück.

BereichWas beim Neustart passiertWas das für die Diagnose bedeutet
Plan CacheAlle gespeicherten Ausführungspläne werden verworfen. Jede Abfrage wird beim nächsten Aufruf neu kompiliert, mit den Parametern genau dieses Aufrufs.Ein unpassender Plan verschwindet, und mit ihm der Beleg, wie er aussah.
Datenseiten im Arbeitsspeicher (Buffer Pool)Der Arbeitsspeicher ist leer, Daten müssen wieder von der Platte gelesen werden.Die ersten Minuten sind eher langsamer. Über diesen Weg hilft ein Neustart nicht.
Verbindungen und offene TransaktionenAlle Sitzungen werden getrennt, offene Transaktionen zurückgerollt.Blockierungen lösen sich auf. Große offene Transaktionen müssen beim Start zurückgerollt werden, das kann dauern.
TempDBWird neu angelegt.Temporäre Objekte, der Version Store in TempDB und Platzprobleme sind weg. (Mit Accelerated Database Recovery liegen Zeilenversionen zusätzlich in der Benutzerdatenbank.)
Speicherzusagen und interne CachesWerden neu aufgebaut.Ein Speicherengpass ist vorübergehend verschwunden.
Zähler in den Systemsichten (DMVs)sys.dm_exec_query_stats, sys.dm_os_wait_stats und die Indexnutzung in sys.dm_db_index_usage_stats beginnen wieder bei null.Ein Vergleich mit dem Zustand vor dem Neustart ist nur möglich, wenn Sie ihn gesichert haben. Entscheiden Sie kurz nach einem Neustart nicht über „ungenutzte“ Indizes.
Query StoreBleibt erhalten, er liegt in der Datenbank.Die wichtigste Spur über den Neustart hinweg, sofern er eingeschaltet war. Bei einem harten Abbruch fehlen die Werte seit dem letzten Speichern (siehe unten).
Errorlog und Sitzung system_healthDas Errorlog beginnt eine neue Datei, die vorherige bleibt als ERRORLOG.1 erhalten. Die Dateien von system_health bleiben bestehen, nur ihr Speicherpuffer ist leer.Meldungen und aufgezeichnete Deadlocks oder lange Sperrwartezeiten sind weiterhin auswertbar. Beide behalten aber nur eine begrenzte Menge: Nach mehreren Neustarts in Folge werden die ältesten Errorlogs verdrängt.

In unserem Lab mit SQL Server 2022 und 2025 war das nach einem geordneten Neustart genau so zu sehen: Die Ausführungsstatistiken im Plan Cache, die Wartezeiten und die Indexnutzung begannen wieder bei null. Von den Datenseiten der Testdatenbank lagen statt 3.364 nur noch 432 im Arbeitsspeicher, die temporären Objekte waren verschwunden. Query Store enthielt dagegen weiterhin alle 6.351 Ausführungen der Testprozedur, und auch Errorlog und system_health-Dateien waren noch da.

Ein Neustart ändert weder die Hardware noch die Datenmenge. Wenn danach alles schnell ist, reichen beide zumindest zeitweise aus. Die Ursache liegt dann eher in einem Zustand, den der Neustart zurückgesetzt hat. Mehr Hardware kann einen solchen Zustand überdecken, beseitigt ihn aber nicht verlässlich. Und Tests auf einer kleinen Testdatenbank, die „viel schneller“ ist, können vor allem zeigen, dass der Optimizer dort andere Pläne wählt.

Fünf Erklärungen, warum es danach schneller ist

Mehrere davon können gleichzeitig zutreffen. Die Belege aus dem nächsten Abschnitt helfen, sie zu unterscheiden.

1. Ein unpassender Plan lag im Cache

SQL Server übersetzt jede Abfrage in einen Ausführungsplan und speichert ihn, damit er beim nächsten Aufruf nicht neu berechnet werden muss. Der Plan entsteht mit den Parameterwerten des ersten Aufrufs. Passt er gut für einen Kunden mit drei Bestellungen, kann er für einen Kunden mit Hunderttausenden Bestellungen sehr teuer sein, und umgekehrt. Dieses Verhalten heißt Parameter Sniffing.

Der Neustart verwirft den Plan. Welcher Plan danach entsteht, entscheidet wieder der erste Aufruf. Kommt zuerst ein „passender“ Wert, ist das Problem scheinbar gelöst, bis der Plan aus anderem Grund neu kompiliert wird, etwa nach einem Statistikupdate oder weil Speicher knapp wird, und ein ungünstiger Wert zuerst kommt. Ein Neustart ist in diesem Fall eine Lotterie um den ersten Aufruf.

Das haben wir im Lab nachgestellt. Eine Bestelltabelle mit 1,25 Millionen Zeilen: Ein Großkunde hat 800.000 Bestellungen, viele kleine Kunden haben eine oder fünf. Eine Procedure wertet die Bestellungen eines Kunden aus (Anzahl, Summe, letzte Bestellung). Nach jedem Neustart riefen wir zuerst den einen, dann den anderen Kunden auf:

Nach dem Neustart zuerst …Entstandener PlanKleiner KundeGroßer Kunde
der kleine KundeIndex Seek mit Key Lookup0,07 ms, 12 Lesezugriffe1.774 bis 1.817 ms, 2,4 Mio. Lesezugriffe
der große KundeScan der ganzen Tabelle107,5 bis 112,0 ms, 20.244 Lesezugriffe298 bis 310 ms, 20.244 Lesezugriffe
Zum Vergleich: jeder Kunde mit eigenem Plan (OPTION (RECOMPILE), ohne Kompilierzeit)jeweils passend0,14 ms, 12 Lesezugriffe302 ms, 20.244 Lesezugriffe

Lesezugriffe sind logische Reads: Jeder Zugriff auf eine 8-KB-Seite zählt, auch wenn dieselbe Seite mehrfach gelesen wird. Deshalb können es mehr Zugriffe sein, als die Tabelle Seiten hat. Was OPTION (RECOMPILE) an Kompilierzeit kostet, zeigt Mal schnell, mal langsam; Zeiten unter einer Millisekunde sind nur grob aufgelöst.

Nach dem ersten Neustart litt der Großkunde: knapp sechsmal so langsam und rund 119-mal so viele Lesezugriffe wie mit passendem Plan. Nach dem zweiten litt der kleine Kunde: aus 12 wurden über 20.000 Lesezugriffe, aus Bruchteilen einer Millisekunde über 100 Millisekunden. Weil kleine Kunden meist die weitaus häufigeren Aufrufe sind, kann gerade diese Variante den ganzen Betrieb ausbremsen. Beide Male war der Neustart „erfolgreich“, nur für unterschiedliche Kunden.

Ohne Neustart genügte übrigens das Leeren des Plan Cache der Datenbank (ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE) für denselben Effekt. Auch manche Konfigurationsänderungen leeren den Plan Cache, etwa an max degree of parallelism, cost threshold for parallelism oder max server memory (Microsoft-Liste). Wird kurz nach so einer Änderung alles schneller, war womöglich das Leeren des Cache die eigentliche Wirkung. Und solange ein Plan im Cache liegt, spielt die Reihenfolge der Aufrufe keine Rolle mehr: Der vorhandene Plan wird weiterverwendet.

Woran Sie es erkennen: Dieselbe Abfrage hat stark schwankende Lesezugriffe oder Laufzeiten (seiten_min und seiten_max in Ergebnis 4 der Sicherungsabfrage), Query Store zeigt mehrere Pläne für dieselbe Abfrage, oder der Plan ist kurz vor dem Einbruch neu entstanden (plan_erstellt). Weiter mit: Mal schnell, mal langsam. Dort finden Sie auch die Gegenmaßnahmen mit ihrem jeweiligen Preis.

2. Eine Sperre hielt andere Vorgänge auf

Eine Sitzung hat eine Transaktion begonnen und nicht abgeschlossen, etwa weil die Anwendung nach einem Fehler kein Rollback sendet. Ihre Sperren halten alle auf, die dieselben Daten ändern wollen, und deren Wartende halten wiederum andere auf. Der Neustart trennt die Sitzung und rollt die Transaktion zurück. Die Kette ist weg, der Fehler in der Anwendung nicht.

Woran Sie es erkennen: Wartetypen LCK_M_…, eine Kette in der Spalte blockiert_von, eine schlafende Sitzung mit lange offener Transaktion (Ergebnis 3). Weiter mit: Blocking und Timeouts.

3. Eine Ressource war aufgebraucht

Große Abfragen warten auf Arbeitsspeicher für Sortierungen und Hash-Operationen, eine lange laufende Transaktion lässt den Version Store in TempDB wachsen, wenn Zeilenversionen genutzt werden (etwa mit Read Committed Snapshot), oder TempDB läuft voll. Nach dem Neustart ist alles wieder frei, bis derselbe Ablauf es erneut verbraucht.

Woran Sie es erkennen: wartende Speicherzusagen und große Werte für TempDB oder Version Store in Ergebnis 1, Wartetypen wie RESOURCE_SEMAPHORE. Weiter mit: Unter Last langsam, Schritt 4.

4. Die Last war ohnehin vorbei

Der Neustart fiel auf das Ende des Monatsabschlusses, oder er hat den großen Bericht abgebrochen, der alles ausgebremst hat. Dann hat nicht der Neustart geholfen, sondern das Ende der Arbeit. Abgebrochen ist sie trotzdem: Der Bericht fehlt, und begonnene Änderungen wurden zurückgerollt.

Woran Sie es erkennen: Laufende Anfragen in Ergebnis 2, ein Vergleich der Last vor und nach dem Neustart, Joblisten und Anwendungsprotokolle. Weiter mit: Unter Last langsam, Schritt 1.

5. Die Ursache lag außerhalb von SQL Server

Wurde der ganze Server neu gestartet, sind auch das Betriebssystem, andere Programme, Treiber und Virenscanner zurückgesetzt. Bei virtuellen Maschinen kann sich zudem die Lage auf dem Host verändert haben.

Woran Sie es erkennen: Hilft nur ein Neustart des ganzen Servers, nicht aber der des SQL-Server-Dienstes? Belegt ein anderer Prozess die CPU oder den Arbeitsspeicher? Ein Hinweis darauf steht in Unter Last langsam, Schritt 2. Weiter mit: Betrieb, Virtualisierung und Monitoring des Servers.

Bevor Sie neu starten: fünf Minuten für die Belege

Manchmal ist der Neustart die richtige Entscheidung, weil der Betrieb sofort weitergehen muss. Dann lohnt es sich, vorher fünf Minuten zu investieren. Ohne diese Belege beginnt die Suche beim nächsten Mal wieder bei null.

  1. Zeitpunkt und Symptom notieren. Was ist langsam, seit wann, wer ist betroffen? Das Befundblatt hilft dabei.
  2. Das Lagebild sichern. Führen Sie die folgende Abfrage aus und speichern Sie die Ergebnisse, bevor Sie neu starten.
  3. Bei hängenden Vorgängen die Kette sichern. Die Abfrage zur Blocking-Kette zeigt, wer am Anfang steht.
  4. Query Store prüfen. Ist er für die betroffene Datenbank eingeschaltet, behält er Laufzeiten und Pläne über den Neustart hinweg. Neue Werte sammelt er zunächst im Arbeitsspeicher und schreibt sie in Abständen auf die Platte, standardmäßig alle 15 Minuten. Vor einem geordneten Herunterfahren schreibt SQL Server die gesammelten Werte weg, sofern nicht Trace Flag 7745 gesetzt ist (Microsoft-Dokumentation). Endet der Prozess dagegen abrupt, etwa bei einem Absturz oder wenn er zwangsweise beendet wird, fehlen die Werte seit dem letzten Speichern, also bis zu 15 Minuten. Mit EXEC sys.sp_query_store_flush_db; in der betroffenen Datenbank schreiben Sie sie sofort weg. Dafür brauchen Sie das Recht ALTER auf die Datenbank (Microsoft-Dokumentation). In unserem Lab fehlten nach einem harten Abbruch die letzten rund 6.300 Ausführungen; mit vorherigem Flush blieben alle erhalten. Prüfen Sie dabei auch, ob Query Store überhaupt schreibt: actual_state_desc in sys.database_query_store_options sollte READ_WRITE zeigen.
  5. Offene Transaktionen ansehen. Ergebnis 3 zeigt, wie lange Transaktionen offen sind und wie viel Protokoll sie geschrieben haben.

Abfrage: Lagebild vor dem Neustart

Abfrage 4: Lagebild vor dem Neustart · nur lesend · geprüft auf SQL Server 2017–2025
/* PSG Stack · SQL Server Performance Ratgeber · Abfrage 4
   Lagebild vor dem Neustart: sichert, was ein Neustart löscht.

   - Nur lesend. Ändert nichts, beendet keine Sitzung, setzt keine
     Zähler zurück.
   - Jede Teilabfrage läuft mit MAXDOP 1, damit die Diagnose den
     belasteten Server nicht zusätzlich mit paralleler Arbeit belastet.
   - Liefert fünf Ergebnisse:
       1 Instanz, Speicher und TempDB
       2 Laufende Anfragen mit Wartetyp und Plan
       3 Offene Transaktionen, auch von schlafenden Sitzungen
       4 Teuerste Einträge im Plan Cache, mit Entstehungszeit des Plans
       5 Wartezeiten seit dem Start der Instanz (Rohwerte)
   - Speichern Sie die Ergebnisse, bevor Sie neu starten: Pläne per
     Klick auf query_plan öffnen und als .sqlplan speichern, übrige
     Ergebnisse per Rechtsklick „Ergebnisse speichern unter“.
   - Rechte: VIEW SERVER STATE
     (ab SQL Server 2022 genügt VIEW SERVER PERFORMANCE STATE).
   - Ab SQL Server 2016. Geprüft im PSG-Lab auf 2017, 2019, 2022 und 2025.
   - Der Plan Cache ist für die ganze Instanz gemeinsam; Ergebnis 4 zeigt
     alle Datenbanken. Ändern Sie bei Bedarf die Sortierung, etwa nach
     Dauer statt CPU. Die Werte zählen seit Entstehung des Plans;
     frisch kompilierte Pläne wirken deshalb klein.
   - tempdb_frei_mb umfasst nur freie Bereiche der Datendateien,
     nicht das Transaktionsprotokoll von TempDB.
*/

/* 1 · Instanz, Speicher und TempDB */
SELECT
    @@SERVERNAME                                               AS server,
    i.sqlserver_start_time                                     AS gestartet,
    SYSDATETIME()                                              AS gesichert_um,
    DATEDIFF(MINUTE, i.sqlserver_start_time, SYSDATETIME())    AS minuten_seit_start,
    i.cpu_count                                                AS logische_cpus,
    i.committed_kb / 1024                                      AS speicher_belegt_mb,
    i.committed_target_kb / 1024                               AS speicher_ziel_mb,
    pm.process_physical_memory_low                             AS speicher_knapp_gemeldet,
    (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants
     WHERE grant_time IS NULL)                                 AS wartende_speicherzusagen,
    tdb.tempdb_benutzerobjekte_mb,
    tdb.tempdb_interne_objekte_mb,
    tdb.tempdb_version_store_mb,
    tdb.tempdb_frei_mb,
    @@VERSION                                                  AS version
FROM sys.dm_os_sys_info AS i
CROSS JOIN sys.dm_os_process_memory AS pm
CROSS JOIN (
    SELECT
        SUM(user_object_reserved_page_count)     * 8 / 1024 AS tempdb_benutzerobjekte_mb,
        SUM(internal_object_reserved_page_count) * 8 / 1024 AS tempdb_interne_objekte_mb,
        SUM(version_store_reserved_page_count)   * 8 / 1024 AS tempdb_version_store_mb,
        SUM(unallocated_extent_page_count)       * 8 / 1024 AS tempdb_frei_mb
    FROM tempdb.sys.dm_db_file_space_usage
) AS tdb
OPTION (MAXDOP 1);

/* 2 · Laufende Anfragen (ohne diese Sitzung) */
SELECT
    r.session_id,
    r.status,
    r.blocking_session_id                                      AS blockiert_von,
    r.wait_type,
    r.wait_time                                                AS wartet_seit_ms,
    r.total_elapsed_time                                       AS laeuft_seit_ms,
    r.cpu_time                                                 AS cpu_ms,
    r.logical_reads                                            AS seiten_gelesen,
    r.granted_query_memory * 8 / 1024                          AS speicherzusage_mb,
    DB_NAME(r.database_id)                                     AS datenbank,
    s.host_name,
    s.program_name,
    s.login_name,
    SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
        (CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(t.text)
              ELSE r.statement_end_offset END
         - r.statement_start_offset) / 2 + 1)                  AS statement,
    r.query_hash,
    r.plan_handle,
    r.query_plan_hash,
    p.query_plan                                               AS plan_aus_cache
FROM sys.dm_exec_requests AS r
JOIN sys.dm_exec_sessions AS s ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS p
WHERE s.is_user_process = 1
  AND r.session_id <> @@SPID
ORDER BY r.total_elapsed_time DESC
OPTION (MAXDOP 1);

/* 3 · Offene Transaktionen, auch von schlafenden Sitzungen */
SELECT
    s.session_id,
    s.status,
    st.is_user_transaction                                     AS explizit_begonnen,
    at.transaction_begin_time                                  AS transaktion_seit,
    DATEDIFF(SECOND, at.transaction_begin_time, SYSDATETIME()) AS offen_seit_s,
    s.last_request_end_time                                    AS letzter_aufruf_ende,
    DB_NAME(dt.database_id)                                    AS datenbank,
    CAST(dt.database_transaction_log_bytes_used / 1048576.0
         AS decimal(18,1))                                     AS log_mb,
    s.host_name,
    s.program_name,
    s.login_name
FROM sys.dm_tran_session_transactions AS st
JOIN sys.dm_tran_active_transactions AS at
     ON at.transaction_id = st.transaction_id
JOIN sys.dm_exec_sessions AS s
     ON s.session_id = st.session_id
LEFT JOIN sys.dm_tran_database_transactions AS dt
     ON dt.transaction_id = st.transaction_id
    AND dt.database_id <> 32767
WHERE st.session_id <> @@SPID
ORDER BY at.transaction_begin_time
OPTION (MAXDOP 1);

/* 4 · Teuerste Einträge im Plan Cache */
SELECT TOP (20)
    qs.creation_time                                           AS plan_erstellt,
    qs.last_execution_time                                     AS zuletzt_ausgefuehrt,
    qs.execution_count                                         AS ausfuehrungen,
    qs.total_worker_time / 1000                                AS cpu_ms_gesamt,
    CAST(qs.total_worker_time / 1000.0 / qs.execution_count
         AS decimal(18,1))                                     AS cpu_ms_je_ausfuehrung,
    CAST(qs.total_elapsed_time / 1000.0 / qs.execution_count
         AS decimal(18,1))                                     AS dauer_ms_je_ausfuehrung,
    CAST(qs.max_elapsed_time / 1000.0 AS decimal(18,1))        AS dauer_ms_max,
    qs.total_logical_reads / qs.execution_count                AS seiten_je_ausfuehrung,
    qs.min_logical_reads                                       AS seiten_min,
    qs.max_logical_reads                                       AS seiten_max,
    DB_NAME(t.dbid)                                            AS datenbank,
    OBJECT_NAME(t.objectid, t.dbid)                            AS objekt,
    SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
        (CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(t.text)
              ELSE qs.statement_end_offset END
         - qs.statement_start_offset) / 2 + 1)                 AS statement,
    qs.query_hash,
    qs.plan_handle,
    qs.query_plan_hash,
    p.query_plan
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS t
OUTER APPLY sys.dm_exec_query_plan(qs.plan_handle) AS p
ORDER BY qs.total_worker_time DESC
OPTION (MAXDOP 1);

/* 5 · Wartezeiten seit dem Start der Instanz (Rohwerte zum Sichern) */
SELECT
    wait_type,
    waiting_tasks_count                                        AS wartevorgaenge,
    wait_time_ms                                               AS wartezeit_ms,
    signal_wait_time_ms                                        AS davon_auf_cpu_ms,
    max_wait_time_ms                                           AS laengster_vorgang_ms
FROM sys.dm_os_wait_stats
WHERE wait_time_ms > 0
ORDER BY wait_time_ms DESC
OPTION (MAXDOP 1);

Beispielausgabe aus unserem Lab (SQL Server 2022, gekürzt auf wichtige Spalten und Zeilen). Eine Sitzung hielt eine Transaktion offen und tat nichts mehr, eine zweite wartete auf deren Sperre, eine dritte rechnete:

Beispielausgabe
-- 2 Laufende Anfragen
session_id status    blockiert_von wait_type   laeuft_seit_ms cpu_ms host_name statement
80         suspended 0             CXSYNC_PORT 26221          52430  hostC     SELECT COUNT_BIG(*) FROM dbo.t2 a CROSS JOIN …
86         suspended 56            LCK_M_X     25211          2      hostB     UPDATE [dbo].[t] set [v] = @1 WHERE [id]=@2

-- 3 Offene Transaktionen
session_id status   explizit_begonnen offen_seit_s letzter_aufruf_ende     log_mb host_name
56         sleeping 1                 29           2026-10-21 14:14:16.293 1.3    hostA
86         running  0                 26           2026-10-21 14:14:19.400 0.0    hostB

-- 4 Teuerste Einträge im Plan Cache (Auszug)
plan_erstellt           ausfuehrungen cpu_ms_je_ausfuehrung seiten_min seiten_max objekt   statement
2026-10-21 14:14:07.137 3             3356.1                158911     158957     p_teuer  SELECT COUNT_BIG(*) FROM …
2026-10-21 14:14:06.760 10            29.7                  2714       2714       p_mittel SELECT v, COUNT_BIG(*) AS n …
2026-10-21 14:14:06.657 200           0.0                   3          3          p_billig SELECT v FROM dbo.t WHERE id = @id

So lesen Sie die Ausgabe: Sitzung 86 wartet mit LCK_M_X auf Sitzung 56. Diese steht in Ergebnis 3 als sleeping mit einer seit 29 Sekunden offenen Transaktion: ein schlafender Headblocker wie in Blocking und Timeouts. Sitzung 80 rechnet parallel und hat in 26 Sekunden schon 52 Sekunden CPU-Zeit verbraucht. In Ergebnis 4 zeigen seiten_min und seiten_max, ob dieselbe Anweisung mal wenig, mal viel liest; liegen beide weit auseinander, lohnt der Blick auf Parameter und Plan. Bei Ad-hoc-Anweisungen bleiben datenbank und objekt leer; objekt bleibt auch leer, wenn Ihr Login keinen Zugriff auf die jeweilige Datenbank hat. Dass Sitzung 86 in Ergebnis 2 suspended und in Ergebnis 3 running heißt, ist kein Widerspruch: Ergebnis 2 zeigt den Zustand der Anfrage, Ergebnis 3 den der Sitzung. Ergebnis 5 beginnt meist mit harmlosen Hintergrund-Waits; ausgewertet wird es später im Vergleich mit einem zweiten Messstand.

Die Abfrage liest nur und belastet den Server wenig. Auf sehr großen Instanzen kann Ergebnis 4 einige Sekunden dauern, weil es den ganzen Plan Cache durchsucht.

Es geht oft kleiner als ein Neustart

Zeigen die Belege auf eine einzelne Ursache, gibt es gezieltere Eingriffe. Jeder davon ist eine Änderung am laufenden System. Sichern Sie vorher die Belege, und stimmen Sie den Eingriff mit dem Betrieb ab.

BefundGezielter EingriffWas Sie wissen sollten
Eine Abfrage mit unpassendem PlanNur diesen Plan entfernen: DBCC FREEPROCCACHE (plan_handle) mit dem plan_handle aus Ergebnis 2 oder 4 (Recht ALTER SERVER STATE) oder sp_recompile für die ProcedureDieselbe Lotterie wie beim Neustart, nur für eine Abfrage. Löst die Ursache nicht; siehe Gegenmaßnahmen in Mal schnell, mal langsam
Ein bekannter guter Plan in Query StorePlan vorübergehend erzwingenMuss beobachtet und wieder aufgehoben werden. Ein vergessener erzwungener Plan kann spätere Verbesserungen verhindern, wie unser Praxisfall zeigt
Eine schlafende Sitzung blockiert alleDie Sitzung nach Rücksprache mit der Anwendung beenden (KILL)Auch hier wird zurückgerollt. Die Anwendung wird das Muster wiederholen, bis der Fehler behoben ist

DBCC FREEPROCCACHE ohne Argument leert den Plan Cache der ganzen Instanz. Das ist für die Pläne fast so grob wie ein Neustart, nur ohne Ausfallzeit, und vernichtet ebenfalls die Belege.

Nach dem Neustart: die Ursache weiterverfolgen

  • Pläne vergleichen. Legen Sie den gesicherten Plan neben den neuen. Unterscheidet sich query_plan_hash? Wie viele Seiten liest die Abfrage jetzt je Ausführung?
  • Query Store auswerten. Die Abfrage „Teuerste Abfragen im Zeitfenster“ zeigt die Zeit vor und nach dem Neustart. In SQL Server Management Studio helfen zusätzlich die Berichte Regressed Queries und Queries With High Variation.
  • Auf die Wiederkehr vorbereitet sein. Legen Sie die Sicherungsabfrage und die Abfrage zur Blocking-Kette bereit. Kommt das Problem zurück, haben Sie den Vergleich in wenigen Minuten.

Und wenn der Neustart regelmäßig hilft?

Manche Betriebe starten SQL Server deshalb jede Woche neu. Als bewusste Überbrückung mit einem Enddatum kann das vertretbar sein. Als Dauerlösung hat es seinen Preis: eine Ausfallzeit, nach jedem Start ein leerer Arbeitsspeicher und neu zu kompilierende Pläne, verlorene Diagnosespuren und eine Ursache, die weiter wächst. Ein Wartungsfenster für den Neustart ist zugleich ein guter Zeitpunkt, die Belege zu sichern.

Den Befund festhalten

Für unseren Lab-Versuch sähe das Befundblatt so aus:

Befundblatt, ausgefüllt für den Neustart-Versuch
Betroffene Aufgabe:   Bestellauswertung eines Kunden ist langsam, nach Neustart wieder schnell
Zeitraum:             Nachgestellt im Lab, SQL Server 2022
Vergleich:            Großkunde 1.777 ms nach "klein zuerst", 302 ms mit eigenem Plan
Belege:               Procedure-Statistik und Plan: derselbe Plan (Seek + Key Lookup)
                      für beide Kunden; 12 Lesezugriffe beim kleinen, 2,4 Mio. beim
                      großen Kunden; nach Neustart mit "groß zuerst" umgekehrtes Bild
Arbeitshypothese:     Parameter Sniffing: Der Plan des ersten Aufrufs passt nicht für
                      den Großkunden
Nächster Schritt:     Gegenmaßnahme für beide Kundenklassen auf demselben Datenstand
                      testen (siehe "Mal schnell, mal langsam", Schritt 5)
Wirkung:              Nach der Änderung beide Kunden in beliebiger Reihenfolge messen,
                      auch nach einem erneuten Neustart

Weitere Diagnosewege