SQL Server Statistiken veraltet
Wenn der Optimizer auf gestern plant – und heute alles stillsteht.
Statistiken veraltet — Parameter Sniffing und Plan-Regression
Gestern 0,5 Sekunden, heute 45: Wenn der Optimizer auf falschen Zahlen plant. Diagnose, Auswege und die Query-Store-Versicherung.
Es ist Donnerstagmorgen, 06:42 Uhr. Bei der Musterwerk GmbH ruft die Frühschicht der Logistik an: Die Kommissionierungs-Maske, die seit Jahren in einer halben Sekunde antwortet, braucht heute 45 Sekunden — pro Position. Am Code hat niemand etwas geändert, das letzte Release ist drei Wochen her. Geändert hat sich nur eines: In der Nacht lief der große Quartals-Datenimport, vier Millionen neue Auftragszeilen, und hinterher hat der SQL Server brav seine Statistiken aktualisiert. Genau dabei hat er der wichtigsten Prozedur des Hauses einen neuen Ausführungsplan verpasst — und der ist für den Normalfall eine Katastrophe.
Willkommen beim unsichtbarsten aller Performance-Probleme: Hier ist nichts kaputt, nichts voll, nichts blockiert — der Server tut exakt, was er soll, nur auf Basis von Zahlen, die nicht (mehr) stimmen, oder eines Plans, der für einen anderen Fall gebaut wurde. Dieser Beitrag aus dem SQL-Server-Notarztwagen (die Triage-Übersicht liegt unter /sql-server-notarztwagen/) erklärt das Dreieck aus Statistiken, Parameter Sniffing und Plan-Regression — und zeigt dir die drei Auswege, vom gezielten Statistik-Update bis zum Plan-Forcing im Query Store.
Was bedeutet das technisch?
Der Optimizer des SQL Servers ist ein Wahrscheinlichkeitsrechner: Für jede Abfrage wählt er aus vielen möglichen Plänen den voraussichtlich billigsten — und „voraussichtlich“ basiert auf Statistiken: Histogrammen über die Datenverteilung der Spalten. Aus ihnen schätzt er, wie viele Zeilen ein Filter liefern wird, und diese Kardinalitätsschätzung entscheidet über alles Weitere: Index Seek oder Table Scan? Nested Loops oder Hash Join? Wie viel Speicher als Grant? Sind die Statistiken veraltet — etwa nach einem Massenimport —, rechnet der Optimizer mit der Welt von gestern und baut Pläne, die zur Welt von heute nicht passen.
Das zweite Puzzleteil ist Parameter Sniffing: Beim ersten Aufruf einer parametrisierten Prozedur schnüffelt der Optimizer am konkreten Parameterwert und optimiert den Plan für genau diesen Wert. Der Plan wandert in den Cache — und alle weiteren Aufrufe verwenden ihn wieder, egal welche Parameter sie mitbringen. Bei gleichmäßig verteilten Daten ist das ein Segen (eine Kompilierung statt tausend); bei schiefen Verteilungen — drei Aufträge beim Kleinkunden, drei Millionen beim Großkunden — wird der geschnüffelte Plan für die halbe Belegschaft zur Falle. Welcher Parameter zuerst kommt, entscheidet der Zufall: Deshalb ist dasselbe System mal pfeilschnell und nach dem nächsten Neustart unerträglich.
Und das dritte Teil: die Plan-Regression. Pläne fliegen regelmäßig aus dem Cache — durch Statistik-Updates (Recompile-Trigger!), Neustarts, Failover, Speicher-Druck, Upgrades. Bei jeder Neukompilierung würfelt der Optimizer neu, mit den dann aktuellen Statistiken und dem dann zufälligen ersten Parameter. Meist kommt derselbe gute Plan heraus — manchmal aber ein schlechterer, und genau das ist die Lead-Szene: Das nächtliche Statistik-Update hat die Neukompilierung ausgelöst, und der neue Plan ist eine Regression. Die drei Mechanismen greifen ineinander, und die Box trennt sie sauber:
|
Abgrenzung: drei Mechanismen, ein Symptom Veraltete Statistiken — der Optimizer schätzt auf alten Zahlen und baut deshalb schlechte Pläne. Heilmittel: aktualisieren. |
|---|
Der Diagnose-Pfad: das Sniffing-Szenario
Die folgende Skizze zeigt den Klassiker im Zeitraffer — und darunter die drei Auswege, die im Rest des Beitrags mit Skripten unterfüttert werden:

Erste Diagnose: konkrete Skripte
Schritt 1 — Wie alt sind die Statistiken wirklich?
|
— Statistik-Alter und Aenderungsvolumen SELECT OBJECT_NAME(s.object_id) AS Tabelle, s.name AS Statistik, sp.last_updated AS ZuletztAktualisiert, sp.rows AS ZeilenBeimUpdate, sp.rows_sampled AS DavonGesampelt, sp.modification_counter AS AenderungenSeitdem FROM sys.stats AS s CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp WHERE OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1 ORDER BY sp.modification_counter DESC; |
|---|
Drei Spalten erzählen die Geschichte: last_updated weit in der Vergangenheit bei hohem modification_counter heißt schlicht veraltet. Spannender ist das Verhältnis rows_sampled zu rows: Wurde eine 100-Millionen-Zeilen-Tabelle mit einem Prozent Stichprobe vermessen, kann die Statistik formal frisch und trotzdem blind für die Ausreißer sein — der Fall für ein gezieltes FULLSCAN. Und ein sehr junges last_updated auf der Problemtabelle, kombiniert mit dem plötzlichen Einbruch heute Morgen? Das ist die Signatur der Lead-Szene: Das Update selbst hat die Neukompilierung ausgelöst.
Schritt 2 — Die Regression im Query Store nachweisen
|
— Abfragen mit mehreren Plaenen und grosser Laufzeit-Spreizung SELECT TOP (10) q.query_id, OBJECT_NAME(q.object_id) AS Prozedur, COUNT(DISTINCT p.plan_id) AS AnzahlPlaene, MIN(rs.avg_duration) / 1000 AS BesteAvgMs, MAX(rs.avg_duration) / 1000 AS SchlechtesteAvgMs FROM sys.query_store_query AS q JOIN sys.query_store_plan AS p ON p.query_id = q.query_id JOIN sys.query_store_runtime_stats AS rs ON rs.plan_id = p.plan_id GROUP BY q.query_id, q.object_id HAVING COUNT(DISTINCT p.plan_id) > 1 ORDER BY MAX(rs.avg_duration) – MIN(rs.avg_duration) DESC; |
|---|
Abfragen mit mehreren Plänen und großer Spreizung zwischen bester und schlechtester Durchschnittslaufzeit sind deine Regressions-Kandidaten — die Lead-Szenen-Prozedur steht hier ganz oben, mit einem 0,5-Sekunden-Plan und einem 45-Sekunden-Plan in der Historie. Noch bequemer geht es grafisch: Der SSMS-Bericht Regressed Queries unter dem Query-Store-Knoten zeigt dieselbe Auswertung mit Plan-Vergleich per Klick. Voraussetzung ist natürlich ein aktivierter Query Store — falls der noch fehlt, ist der entsprechende GRÜN-Beitrag deine nächste Lektüre, denn ohne ihn ist jede Regressions-Diagnose Archäologie ohne Fundstücke.
Schritt 3 — Eingreifen: Update, Recompile oder Forcing
|
— Ausweg 1: Statistiken der Problemtabelle gezielt aktualisieren UPDATE STATISTICS dbo.Auftraege WITH FULLSCAN;
— Ausweg 2: Abfrage bei jedem Aufruf frisch kompilieren SELECT … FROM dbo.Auftraege WHERE KundenNr = @KundenNr OPTION (RECOMPILE);
— Ausweg 3: bewaehrten Plan per Query Store erzwingen EXEC sp_query_store_force_plan @query_id = 4711, @plan_id = 42; — Aufheben spaeter mit: sp_query_store_unforce_plan |
|---|
Die häufigsten Ursachen — und was du dagegen tust
1. Auto Update ist zu träge für deine Tabellen
Das automatische Statistik-Update springt erst an, wenn sich „genug“ geändert hat — und auf alten Kompatibilitätsgraden hieß „genug“: 20 Prozent der Tabelle. Bei einer 50-Millionen-Zeilen-Tabelle sind das zehn Millionen Änderungen, bevor der Server reagiert; bis dahin plant er munter auf Museumszahlen. Seit Compatibility Level 130 gilt eine dynamische Schwelle, die mit der Tabellengröße sinkt — Grund genug, den Kompatibilitätsgrad alter Datenbanken zu prüfen. Aber selbst die moderne Schwelle ist ein Sicherheitsnetz, kein Wartungsplan: Wer nach Massenimporten ein gezieltes UPDATE STATISTICS in den Import-Job einbaut und eine reguläre Statistik-Wartung fährt, wartet gar nicht erst, bis das Netz greift.
2. Klassisches Sniffing — der atypische erste Aufruf
Das Szenario aus der Skizze: Der zufällige erste Aufruf nach einer Neukompilierung prägt den Plan für alle. Die Werkzeugwahl hängt vom Aufrufmuster ab: Läuft die Abfrage selten mit extrem unterschiedlichen Parametern (Suchmasken, Reports), ist OPTION (RECOMPILE) ehrlich und billig. Läuft sie hochfrequent, lohnt der Umbau: OPTIMIZE FOR mit einem repräsentativen Wert, getrennte Code-Pfade für Klein- und Großfälle — oder auf aktuellen Versionen die eingebaute Hilfe der Parameter Sensitive Plan Optimization (ab SQL Server 2022), die für stark schiefe Parameter mehrere Plan-Varianten parallel vorhält. Was nicht hilft: hoffen, dass beim nächsten Neustart zufällig der richtige Parameter zuerst kommt.
3. Das Ascending-Key-Problem — die Gegenwart fehlt im Histogramm
Die Spezialität von Datums- und ID-Spalten: Neue Zeilen landen am oberen Ende — jenseits des letzten Histogramm-Schritts. Fragt die Anwendung nach „Aufträge von heute“, schätzt der Optimizer auf Basis der alten Statistik: praktisch null Zeilen — und baut einen Mini-Plan für eine Abfrage, die in Wahrheit zehntausende Zeilen liefert. Das Muster erkennst du an Abfragen auf aktuelle Zeiträume, die im Plan absurd niedrige geschätzte Zeilenzahlen zeigen. Moderne Kardinalitätsschätzer entschärfen das Problem teilweise; die robuste Therapie bleibt, die Statistiken der betroffenen Spalten häufiger zu aktualisieren — notfalls stündlich per Job, das kostet auf einer einzelnen Statistik fast nichts.
4. Die Stichprobe ist zu klein — frisch, aber blind
Der Befund aus Schritt eins: rows_sampled ist nur ein Bruchteil von rows. Bei gleichmäßigen Daten kein Problem — bei schiefen Verteilungen übersieht die Stichprobe genau die Ausreißer, um die es geht: den einen Großkunden, die eine Massen-Kategorie. Die Statistik ist dann formal aktuell und trotzdem falsch. Therapie: für die betroffenen Statistiken UPDATE STATISTICS … WITH FULLSCAN — und damit das nicht bei der nächsten automatischen Aktualisierung wieder verwässert, auf neueren Versionen PERSIST_SAMPLE_PERCENT = ON setzen, das die Abtastrate festschreibt. Gezielt auf Problemfälle, nicht als Gießkanne: FULLSCAN über eine Multi-Terabyte-Datenbank ist Ressourcen-Folklore.
5. Regression nach Wartung, Failover oder Upgrade
Jedes Ereignis, das den Plan-Cache leert oder Neukompilierungen auslöst, ist ein Regressions-Risiko: das nächtliche Statistik-Update (Lead-Szene), der Failover aufs Replica, das Einspielen eines CU, und ganz besonders Upgrades mit Wechsel des Kompatibilitätsgrads — denn damit wechselt auch der Kardinalitätsschätzer, und der rechnet manche Abfragen schlicht anders. Die professionelle Antwort heißt Query Store: vor der Änderung aktivieren und Baseline sammeln, nach der Änderung den Regressed-Queries-Bericht abarbeiten und Ausreißer per Plan-Forcing auf den bewährten Plan nageln, bis die Ursache verstanden ist. So wird aus dem Upgrade-Roulette ein kontrollierter Prozess — die Details liefert der Query-Store-Beitrag der GRÜN-Stufe.
Was du auf keinen Fall tun solltest
|
Die drei häufigsten Anfängerfehler 1. Bei „plötzlich langsam“ sofort an Hardware oder Indizes denken — statt zu fragen, ob ein Plan gekippt ist. Die Signatur ist unverkennbar: gleiche Abfrage, gleiches Datenvolumen, zehnfache Laufzeit. |
|---|
Nachsorge: Damit es nicht wiederkommt
Drei strukturelle Maßnahmen nach dem akuten Fall: Erstens, eine geplante Statistik-Wartung etablieren — Ola Hallengrens IndexOptimize mit Statistik-Pflege ist der De-facto-Standard, ergänzt um gezielte Updates direkt nach Massenimporten. Wie Statistik- und Index-Wartung zusammenspielen (und warum ein Index-Rebuild die Statistik gratis mitliefert, REORGANIZE aber nicht), steht im Index-Wartungs-Beitrag der GRÜN-Stufe. Zweitens, den Query Store auf jeder Produktions-Datenbank aktivieren — als Flugschreiber für Pläne und als Werkzeugkasten gegen Regressionen. Drittens, geforcte Pläne und Spezial-Hints inventarisieren: eine simple Liste mit Abfrage, Datum, Grund und Wiedervorlage-Termin verhindert, dass die Pflaster von heute die Altlasten von morgen werden.
Vertiefung in der Buchreihe
Kardinalitätsschätzung, Plan-Mechanik und sniffing-resistenter Code füllen in der Buchreihe „SQL Server in der Praxis“ eigene Kapitel — alle Bände in der Übersicht auf der SQL-Pillar-Seite unter /sql-server/.
|
Empfohlene Bände Band 1: Performance & Troubleshooting — Statistiken und Histogramme im Detail, Kardinalitätsschätzer alt und neu, Plan-Cache-Mechanik, Query Store von der Aktivierung bis zur Forcing-Strategie. |
|---|
Verwandte Notfälle im Notarztwagen
Schätzfehler strahlen in viele Richtungen aus — diese Beiträge aus dem Cluster lohnen den Blick:
Drei Wege, wenn der Plan jetzt gerade gekippt ist
Festpreis-Analyse
Die Performance springt unberechenbar zwischen gut und katastrophal, nach jedem Wartungswochenende zittert das Team, und niemand traut sich mehr an den Kompatibilitätsgrad? Dafür gibt es die Festpreis-Analyse: Ich werte Statistik-Zustand und Sampling-Qualität aus, identifiziere Sniffing-Kandidaten und Regressions-Muster im Query Store, prüfe Wartungsplan und Auto-Update-Konfiguration und liefere einen Bericht mit priorisierten Maßnahmen — von den Sofort-Fixes bis zur dauerhaften Statistik-Strategie. Zum vereinbarten Festpreis, ohne Stundenzähler.
Akut-Hotline
Wenn die zentrale Prozedur gerade jetzt im 45-Sekunden-Modus läuft und die Frühschicht wartet: anrufen. Halte einen Zugang mit VIEW-SERVER-STATE-Rechten bereit und die Antwort auf die Schlüsselfrage — was lief heute Nacht? Import, Wartung, Failover, Patch? Mit Query Store ist der gekippte Plan in Minuten identifiziert und per Forcing entschärft; ohne dauert es etwas länger, geht aber auch.
Selbst-Diagnose-Kit
Du willst das Dreieck aus Statistiken, Sniffing und Regression selbst beherrschen? Das Diagnose-Kit bündelt die Skripte aus diesem Beitrag — Statistik-Alters-Analyse, Regressions-Suche im Query Store, die drei Auswege als Vorlagen — als ausführbares Paket mit Auswertungshilfe und einer Entscheidungsmatrix, welcher Ausweg zu welchem Aufrufmuster passt.
|
Ein letzter Tipp Prüfe heute zwei Dinge auf deiner wichtigsten Datenbank: Ist der Query Store aktiv? Und steht der Kompatibilitätsgrad noch auf einem Wert von vor zehn Jahren? Beides kostet je eine Abfrage und entscheidet darüber, ob du beim nächsten gekippten Plan in fünf Minuten den Beweis samt Gegenmittel hast — oder eine Woche lang raten darfst, was da eigentlich passiert ist. |
|---|
Häufige Fragen (FAQ)
Diese Sektion ist auf häufige Suchanfragen abgestimmt. Sie hilft dir, schnell den Punkt zu finden, der dir gerade akut weiterhilft — und sie ist gleichzeitig die Eingabe für das FAQPage-Schema auf der Seite.
Wie aktualisiere ich SQL-Server-Statistiken?
Gezielt mit UPDATE STATISTICS Tabelle bzw. UPDATE STATISTICS Tabelle(StatistikName) — wahlweise mit WITH FULLSCAN für die vollständige Abtastung oder mit SAMPLE-Prozentsatz. Für die ganze Datenbank existiert sp_updatestats, das allerdings nur ein Mini-Sample nutzt und alles anfasst, was sich auch nur minimal geändert hat — als Akut-Werkzeug brauchbar, als Dauerstrategie zu grob. Die nachhaltige Lösung ist eine geplante Statistik-Wartung, etwa über Ola Hallengrens IndexOptimize mit aktivierter Statistik-Pflege. Wichtig: Nach einem UPDATE STATISTICS werden betroffene Pläne automatisch neu kompiliert — genau das ist oft der gewünschte Effekt.
Was ist Parameter Sniffing?
Beim ersten Aufruf einer parametrisierten Prozedur oder Abfrage „schnüffelt“ der Optimizer am konkreten Parameterwert und baut den Plan passend für genau diesen Wert — dann wandert der Plan in den Cache und wird für alle weiteren Aufrufe wiederverwendet, egal welche Parameter kommen. Das ist meist effizient (eine Kompilierung statt tausend), wird aber zum Problem bei schiefen Datenverteilungen: Kompiliert der Plan für einen Kleinkunden mit 3 Zeilen und kommt danach der Großkunde mit 3 Millionen, läuft die Abfrage mit einem grotesk unpassenden Plan. Sniffing ist also ein Feature mit Nebenwirkung — die Auswege stehen in diesem Beitrag.
Wann sollte ich UPDATE STATISTICS mit FULLSCAN nutzen?
FULLSCAN tastet die komplette Tabelle ab statt einer Stichprobe — die Statistik wird maximal genau, der Lauf entsprechend teuer. Sinnvoll ist das bei Tabellen mit schiefen Verteilungen, bei denen Stichproben die Ausreißer verfehlen, bei hartnäckigen Schätzfehlern trotz frischer Statistik und generell bei kleinen bis mittleren Tabellen, wo der Vollscan ohnehin billig ist. Bei Multi-Terabyte-Tabellen ist nächtliches FULLSCAN dagegen Ressourcenverschwendung — dort reicht meist ein erhöhtes SAMPLE, gezielt auf die kritischen Statistiken. Faustregel: FULLSCAN als gezieltes Werkzeug für Problemfälle, nicht als Gießkanne über die ganze Datenbank.
Wie aktiviere ich Auto Update Statistics?
Über ALTER DATABASE [DB] SET AUTO_UPDATE_STATISTICS ON — wobei die Option in fast allen Datenbanken ohnehin aktiv ist, weil sie der Standard ist. Interessanter sind die Begleitoptionen: AUTO_UPDATE_STATISTICS_ASYNC lässt die laufende Abfrage nicht auf das Update warten (auf OLTP-Systemen oft die bessere Wahl), und der Kompatibilitätsgrad entscheidet über die Auslöseschwelle — ab Compatibility Level 130 gilt eine dynamische, mit der Tabellengröße sinkende Schwelle statt der trägen alten 20-Prozent-Regel. Wichtig zu wissen: Auto Update ist ein Sicherheitsnetz, kein Ersatz für geplante Statistik-Wartung.
Was ist Plan-Forcing im Query Store?
Plan-Forcing heißt: Du legst im Query Store fest, dass eine Abfrage einen bestimmten, bewährten Ausführungsplan verwenden muss — der Optimizer darf nicht mehr frei wählen. Das ist die schnellste seriöse Antwort auf eine Plan-Regression: Im Query-Store-Bericht den guten alten Plan markieren, „Force Plan“ klicken (oder sp_query_store_force_plan ausführen), und die Abfrage läuft sofort wieder mit dem bewährten Plan — ohne Code-Änderung, ohne Wartungsfenster. Forcing ist allerdings ein Pflaster mit Ablaufdatum: Die Ursache der Regression gehört trotzdem diagnostiziert, und geforcte Pläne gehören auf eine Wiedervorlage-Liste.
Wie erkenne ich Plan-Regression?
Das Erkennungszeichen ist eine Abfrage, deren Laufzeit oder CPU-Verbrauch pro Ausführung sprunghaft steigt, ohne dass sich Code oder Datenvolumen wesentlich geändert haben — meist nach einem Statistik-Update, einem Neustart, einem Failover oder einem Upgrade. Mit aktiviertem Query Store ist der Nachweis trivial: Der Bericht „Regressed Queries“ in SSMS zeigt Abfragen mit verschlechterter Performance samt Plan-Historie, und im Plan-Vergleich siehst du schwarz auf weiß, was gekippt ist (Scan statt Seek, Hash statt Loop). Ohne Query Store bleibt nur der mühsame Vergleich über dm_exec_query_stats — einer von vielen Gründen, ihn zu aktivieren.
Wann hilft OPTION (RECOMPILE)?
OPTION (RECOMPILE) lässt eine Abfrage bei jedem Aufruf frisch kompilieren — der Plan passt damit immer exakt zu den aktuellen Parametern, und Parameter Sniffing ist als Problem komplett ausgeschaltet. Der Preis: CPU-Kosten bei jeder einzelnen Ausführung und kein wiederverwendbarer Plan im Cache. Sinnvoll ist das Hint deshalb für Abfragen, die selten laufen, aber dramatisch unterschiedliche Parameter-Welten bedienen — der Monats-Report, die flexible Suchmaske. Für eine Prozedur, die tausendmal pro Minute feuert, ist RECOMPILE dagegen Gift: Dort sind stabile Pläne, saubere Statistiken oder gezieltes Plan-Forcing die richtigen Werkzeuge.
