Fallstudie: Musterwerk GmbH:
Der Normalfall — und warum "solide konfiguriert" noch lange nicht "optimal" bedeutet
Musterwerk GmbH: Kein Horrorfall, aber viel Luft nach oben
Nicht jede SQL Server Performance-Analyse beginnt mit einer Katastrophe. Die Musterwerk GmbH — mittelständisches Fertigungsunternehmen, rund 200 Mitarbeiter, Microsoft Dynamics AX 2012 R3 als ERP-System — hat solide Hardware, einen gepflegten Server und einen Administrator, der weiß, was er tut. Und trotzdem: Die Nutzer klagen. Berichte brauchen zu lange. Und während des Morgen-Peaks kommt es regelmäßig zu Hängern, die sich niemand erklären kann.
Genau dieser Fall ist der interessanteste — nicht, weil er dramatisch wäre, sondern, weil er so verdammt häufig ist. Schätzungsweise 70 Prozent aller SQL Server-Installationen befinden sich in diesem Zustand: nicht katastrophal, aber weit unter Potenzial. Wenn du nach Kapitel 33 (Sparfuchs & Partner) wieder auf den Boden der Tatsachen willst, ist Kapitel 32 der richtige Ort. Hier brennt nichts. Hier wird nur verschwendet.
Die Analyse-Methodik folgt dem Phasenmodell aus Kapitel 31: Symptomaufnahme, Baseline lesen, Engpass isolieren, Ursache verstehen, Maßnahme umsetzen, Ergebnis validieren. Das Ergebnis dieser konkreten Analyse: fünf Befunde, vier davon mit sofort messbaren Verbesserungen — und ein fünfter, der zeigt, warum Monitoring keine Option, sondern Pflicht ist.
|
Praxisbeispiel: Ausgangslage SQLPROD-ERP01 |
|---|
|
Instanz: SQLPROD-ERP01.musterwerk.local | SQL Server 2022 (CU14, Build 16.0.4145.4) |
|
Betriebssystem: Windows Server 2022 Datacenter | Plattform: VMware vSphere 8.0 |
|
Hardware: 16 vCPUs (2 NUMA-Knoten × 8 Kerne) | 64 GB RAM | max server memory = 56.000 MB |
|
Anwendung: Microsoft Dynamics AX 2012 R3 | avg. 185 gleichzeitige Sessions, Peak 312 |
|
Datenbank: ErpProd 824 GB, ErpArch 312 GB | Messzeitraum: 15.11.2025, 07:30–09:30 Uhr |
|
Anlass: Nutzer berichten über langsame Morgenanmeldung und hängende Berichte |
Phase 1: Symptome sammeln — bevor die erste DMV geöffnet wird
Die erste halbe Stunde einer Performance-Analyse gehört dem Gespräch, nicht dem SQL Server Management Studio. Welche Nutzer klagen? Seit wann? Unter welcher Last? Gibt es Tagesmuster? Am Ende von zehn Minuten mit dem kaufmännischen Leiter und dem ERP-Projektleiter ergibt sich ein klares Bild:
Das sind drei verschiedene Symptome — und der wichtige Punkt ist: Sie haben drei verschiedene Ursachen. Wer glaubt, "der Server ist zu langsam" sei eine ausreichende Diagnose, wird falsche Maßnahmen ergreifen. Mehr RAM kaufen, wenn das eigentliche Problem zu wenige TempDB-Dateien sind, ist teuer und wirkungslos.
Phase 2 zeigt das bekannte Bild: keine strukturierte Baseline, kein historisches Monitoring. Was es gibt: Windows-Ereignisprotokoll, SQL Server Error Log, und die kumulativen Wait Statistics seit dem letzten Neustart — der laut Error Log vor neun Tagen stattfand. Kapitel 9 erklärt den Umgang mit kumulativen Wait Statistics im Detail; hier nutzen wir sie als ersten Kompass.
Serverkonfiguration: Was gut ist — und was nicht
Bevor man in Wait Statistics oder DMV-Analysen eintaucht, lohnt ein Blick auf die Konfiguration. Die sp_configure-Werte von SQLPROD-ERP01 zeigen ein gemischtes Bild: einige Dinge wurden durchdacht, andere sind offensichtlich nie angefasst worden.
-- Konfigurationsbewertung SQLPROD-ERP01
-- Abfrage der relevanten sp_configure-Einstellungen mit Soll-Ist-Vergleich
SELECT
name,
value_in_use,
description
FROM sys.configurations
WHERE name IN (
'max server memory (MB)', -- Soll: 56.000 MB — korrekt gesetzt
'max degree of parallelism', -- Soll: 8 für 2×8-vCPU-NUMA — korrekt
'cost threshold for parallelism', -- Soll: 50+ — ist noch 25 (zu niedrig)
'optimize for ad hoc workloads' -- Soll: 1 — ist 0 (nicht aktiviert)
)
ORDER BY name;
-- Ergebnisse:
-- max server memory (MB) : 56.000 → OK (8 GB Headroom für OS)
-- max degree of parallelism : 8 → OK (passend für 2×8-NUMA)
-- cost threshold for parallelism : 25 → ANPASSEN (zu niedrig für ERP)
-- optimize for ad hoc workloads : 0 → AKTIVIEREN (Monitoring-Agent)
Zwei Einstellungen sind korrekt — das ist keine Selbstverständlichkeit. max server memory wurde bewusst auf 56.000 MB gesetzt und lässt dem Betriebssystem knapp 8 GB Reserve. MAXDOP 8 passt exakt zur 2×8-vCPU-NUMA-Topologie (wie in Kapitel 5 beschrieben: MAXDOP = Anzahl logischer Kerne pro NUMA-Knoten, maximal 8).
Zwei Einstellungen sind suboptimal: Cost Threshold for Parallelism von 25 ist für ein ERP-System mit gemischter OLTP-Workload zu niedrig — einfache OLTP-Queries werden unnötig parallelisiert, was CXPACKET-Waits erzeugt. Und "Optimize for Ad Hoc Workloads" ist deaktiviert, obwohl ein SolarWinds DPA-Monitoring-Agent hunderte Ad-hoc-Abfragen pro Minute generiert, die den Plan-Cache unnötig aufblähen.
|
Setting |
Ist-Wert |
Empfehlung |
Status |
Begründung |
|---|---|---|---|---|
|
max server memory |
56.000 MB |
56.000 MB |
OK |
8 GB Headroom für OS reicht |
|
MAXDOP |
8 |
8 |
OK |
Passend für 2×8-vCPU-NUMA-Topologie |
|
Cost Threshold for Parallelism |
25 |
50 |
ANPASSEN |
ERP-OLTP-Queries werden unnötig parallelisiert |
|
Optimize for Ad Hoc Workloads |
OFF |
ON |
AKTIVIEREN |
Monitoring-Agent erzeugt viele Ad-hoc-Queries |
|
RCSI (ErpProd) |
OFF |
ON |
KRITISCH |
Reader blockieren Schreiber — Tagesabschluss betroffen |
|
TempDB-Dateien |
4 |
16 |
KRITISCH |
16 vCPUs = 16 TempDB-Dateien empfohlen (max 8/NUMA) |
|
Query Store (ErpProd) |
OFF |
ON |
AKTIVIEREN |
Planstabilität und Parameter-Sniffing-Diagnose |
|
Backup Compression |
ON |
ON |
OK |
Gut konfiguriert |
Konfigurationsbewertung SQLPROD-ERP01 — Soll-Ist-Vergleich
120 Minuten Perfmon: Drei Episoden, die alles erklären
Der Messzeitraum vom 15.11.2025, 07:30 bis 09:30 Uhr deckt genau das ab, was die Nutzer beschreiben: die Morgenanmeldewelle, den Tagesabschluss-Bericht und den anschließenden Normalbetrieb. 1.440 Messpunkte im 5-Sekunden-Intervall — genug Auflösung, um Ereignisse auf die Minute zu rekonstruieren.
CPU: Zweigipfliger Verlauf mit klarer Ursache
Die CPU-Zeitreihe zeigt zwei markante Peaks, die auf den ersten Blick beunruhigend wirken — bei näherer Betrachtung aber beide erklärbar und behebbar sind:
|
Indikator |
Min |
Avg |
p95 |
Max |
Einordnung |
|---|---|---|---|---|---|
|
% Processor Time (System) |
0,3 % |
18,4 % |
39,2 % |
71,6 % |
Moderat — System unauffällig |
|
% Processor Time (sqlservr) |
1 % |
112,7 % |
248,3 % |
389,4 % |
Erheblich — avg. 7 von 16 Kernen |
|
Processor Queue Length |
0 |
0,4 |
3 |
11 |
Spitzen bei Morgenanmeldung |
|
Batch Requests/sec |
12 |
618 |
2.340 |
4.891 |
Breite Spanne — Batch-Jobs sichtbar |
|
SQL Compilations/sec |
0 |
1,4 |
8,2 |
34,7 |
Erhöht — Ad hoc Workloads nicht aktiv |
|
Full Scans/sec |
0,1 |
8,3 |
47,6 |
214,9 |
Mäßig erhöht — fehlender Index SalesLine |
|
Workfiles Created/sec |
0 |
3,1 |
18,4 |
122,7 |
Spitzen beim Reporting-Bericht |
CPU-Indikatoren SQLPROD-ERP01 — Messzeitraum 15.11.2025, 07:30–09:30 Uhr
Peak 1 um 07:47 Uhr: CPU steigt auf 389,4 % (= 24 von 16 Kernen überbucht durch Hyper-Threading). Ursache: 185 AX-Nutzer loggen sich innerhalb von zwei Minuten ein — jeder löst eine Initialisierungssequenz aus. Der Processor Queue Length steigt auf 11. Die Sessions warten auf CPU-Zeit, nicht auf IO. Das ist ein Signal Wait im Sinne von Kapitel 9 — der Scheduler ist überlastet.
Die Compilations/sec steigen in dieser Phase auf 34,7 pro Sekunde. Bei avg. 618 Batches/sec entspricht das einer Kompilierungsrate von 5,6 % — rund 70-mal höher als in einem gut konfigurierten OLTP-System. Ursache: "Optimize for Ad Hoc Workloads" ist deaktiviert, der Monitoring-Agent erzeugt hunderte Ad-hoc-Abfragen, und jede davon belegt Plan-Cache-Einträge, die schnell wieder verdrängt werden — und beim nächsten Aufruf neu kompiliert werden müssen.
Peak 2 um 08:53 Uhr korrespondiert exakt mit dem Tagesabschluss-Bericht (dazu mehr in Befund 3). Die CPU-Last entsteht hier nicht durch echte Rechenlast, sondern durch wartende Sessions, die offene Verbindungen halten und den Scheduler-Druck in die Höhe treiben.
Memory: Solide — aber Vorsicht bei Grants
Die Memory-Situation ist das Highlight dieser Analyse: Der PLE-Wert (Page Life Expectancy) liegt im Durchschnitt bei 8.347 Sekunden. Das ist außergewöhnlich hoch und bedeutet: Eine Datenbankseite, die einmal in den 54-GB-Buffer-Pool geladen wird, bleibt dort durchschnittlich 2,3 Stunden. Das ERP-System hat einen recht kompakten "heißen" Datensatz — Stammdaten, aktuelle Aufträge und Bestandsdaten passen nahezu vollständig in den Arbeitsspeicher.
|
Indikator |
Min |
Avg |
p95 |
Max |
Einordnung |
|---|---|---|---|---|---|
|
Page Life Expectancy (PLE) |
4.820 s |
8.347 s |
11.203 s |
12.819 s |
Exzellent — Buffer Pool sehr stabil |
|
Available MBytes (OS) |
4.120 MB |
5.847 MB |
7.203 MB |
9.411 MB |
Gut — OS hat ausreichend Headroom |
|
Pages/sec (Memory) |
0 |
83 |
2.104 |
8.820 |
Spitzen bei Berichten — noch ok |
|
Memory Grants Pending |
0 |
0 |
2 |
7 |
Moderate Spitzen beim Reporting |
|
Total Server Memory |
53.412 MB |
54.917 MB |
55.801 MB |
56.003 MB |
An Grenze — kein struktureller Druck |
|
Lazy Writes/sec |
0 |
0,2 |
1,4 |
12,3 |
Sehr niedrig — Buffer Pool stabil |
Memory-Indikatoren SQLPROD-ERP01 — Messzeitraum 15.11.2025, 07:30–09:30 Uhr
Der PLE-Wert erklärt auch, warum trotz des fehlenden Indexes auf SalesLine.OrderDate die IO-Latenzen akzeptabel bleiben: Die Table Scans lesen aus dem Cache, nicht von Disk. Das ist ein wichtiger Punkt für die Priorisierung — ein fehlender Index ist auf diesem Server ein CPU- und Locking-Problem, kein IO-Problem.
Bedenklich: Die Memory Grants Pending steigen während des Reporting-Jobs kurzfristig auf 7. Sieben Queries stehen in einer Warteschlange und warten auf einen Memory-Grant für ihre Hash-Joins und Sorts. Das ist heute noch kein Notfall. Wenn die Datenmenge wächst, wird es einer. Kapitel 12 erklärt das Mechanismus von Memory Grants und Spills im Detail — hier notieren wir es als "Monitoring-Pflicht".
Befund 1: TempDB-Allokierungs-Contention — die Zeitbombe im Keller
Der dominante Wait Type auf SQLPROD-ERP01 ist nicht das, was die meisten Administratoren vermuten würden. Weder PAGEIOLATCH_SH noch LCK_M_S führen die Liste an — sondern PAGELATCH_EX auf TempDB-Allokierungsseiten. Kumuliert seit dem letzten Neustart: 3.824 Sekunden Wartezeit, verteilt auf 84 Millionen Warteoperationen.
Um zu verstehen, warum das passiert, muss man kurz in die Interna von TempDB eintauchen — Kapitel 13 behandelt das ausführlich, hier die Kurzfassung: TempDB verwaltet intern sogenannte Allocation Bitmaps (PFS, GAM, SGAM). Jede dieser speziellen Seiten deckt 64.000 Datenbankseiten ab (ca. 500 MB). Wenn eine temporäre Tabelle oder ein Workfile angelegt wird, muss SQL Server eine dieser Bitmap-Seiten mit einem exklusiven Latch (PAGELATCH_EX) sperren, um Allokierungseinträge zu schreiben.
Das Problem auf SQLPROD-ERP01: Nur vier TempDB-Datendateien auf einem System mit 16 logischen CPUs. 185 Sessions beim Morgen-Peak, aufgeteilt auf vier Dateien — das sind rund 46 Sessions pro PFS/GAM-Seite, die gleichzeitig um dieselbe Allokierungs-Bitmap konkurrieren. Das Ergebnis ist ein klassischer Latch-Stau.
-- TempDB-Contention diagnostizieren
-- Zeigt aktuelle PAGELATCH-Waits auf TempDB-Allokierungsseiten
SELECT
session_id,
wait_type,
wait_duration_ms,
resource_description,
blocking_session_id
FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE 'PAGELATCH%'
AND resource_description LIKE '2:%' -- Datenbankid 2 = TempDB
ORDER BY wait_duration_ms DESC;
-- Wenn hier viele Zeilen auftauchen (>10) und die PFS/GAM-Seiten
-- (Seiten-IDs: 1, 2, 3, 8, 9 usw.) als resource_description erscheinen,
-- ist TempDB-Allokierungs-Contention die Ursache.
-- Aktuelle TempDB-Dateiverteilung prüfen:
SELECT
name,
size * 8 / 1024 AS size_mb,
physical_name
FROM tempdb.sys.database_files
WHERE type = 0; -- nur Datendateien, nicht die Log-Datei
-- Auf SQLPROD-ERP01: nur 4 Dateien bei 16 logischen Kernen
-- Empfehlung: 1 Datei pro Kern, max 8 pro NUMA-Knoten
-- Ziel: 16 Dateien (8 pro NUMA-Knoten)
Die aktuellen vier Dateien zeigen alle ähnliche kumulative PAGELATCH_EX-Wartezeiten: 18,4 s, 17,9 s, 19,1 s und 18,8 s. Das ist gleichmäßige Contention — die Last verteilt sich auf alle vier Dateien, aber vier Dateien reichen einfach nicht für 16 logische Kerne.
Die Lösung ist strukturell einfach und dauert ohne Neustart keine 15 Minuten: 12 weitere TempDB-Datendateien hinzufügen, alle auf gleiche Größe setzen. Kapitel 13 erklärt, warum gleiche Dateigrößen entscheidend sind (Proportional Fill-Algorithmus).
-- TempDB: 12 weitere Datendateien hinzufügen
-- Kein Neustart erforderlich — wirkt sofort nach der Ausführung
-- Alle Dateien auf F:\SQLTempDB\ (bereits für TempDB konfiguriert)
ALTER DATABASE tempdb ADD FILE (
NAME = 'tempdb_mssql_5',
FILENAME = 'F:\SQL\SQLTempDB\tempdb_mssql_5.ndf',
SIZE = 40960MB,
FILEGROWTH = 1024MB
);
-- (für tempdb_mssql_6 bis tempdb_mssql_16 wiederholen)
-- Bestehende Dateien auf gleiche Größe angleichen (Proportional Fill)
ALTER DATABASE tempdb MODIFY FILE (NAME='tempdb', SIZE=40960MB);
ALTER DATABASE tempdb MODIFY FILE (NAME='tempdb_mssql_2', SIZE=40960MB);
ALTER DATABASE tempdb MODIFY FILE (NAME='tempdb_mssql_3', SIZE=40960MB);
ALTER DATABASE tempdb MODIFY FILE (NAME='tempdb_mssql_4', SIZE=40960MB);
-- Ergebnis: 16 Dateien × 40 GB = 640 GB TempDB-Kapazität
-- PAGELATCH_EX-Contention sinkt theoretisch um ~75 % (Divisor 4× größer)
|
Tipp: TempDB-Faustregel für die Praxis |
|---|
|
Anzahl TempDB-Datendateien = Anzahl logischer CPU-Kerne, maximal 8 pro NUMA-Knoten. |
|
Auf SQLPROD-ERP01: 16 Kerne, 2 NUMA-Knoten → 8 Dateien pro NUMA-Knoten = 16 Dateien gesamt. |
|
Alle Dateien müssen gleich groß sein (Proportional Fill — SQL Server füllt größere Dateien bevorzugt). |
|
SQL Server 2016+ setzt TempDB-Dateien automatisch korrekt, wenn dies bei der Installation konfiguriert wird. |
|
Weitere Details zur TempDB-Dimensionierung in Kapitel 13. |
Befund 2: Parameter Sniffing — 15.412 CPU-Sekunden pro Stunde für nichts
Der zweite große Befund ist versteckter — und teurer. Die meistgenutzte gespeicherte Prozedur des ERP-Systems, usp_GetKundenBestellungen, zeigt in sys.dm_exec_query_stats eine extreme Streuung: avg. Ausführungszeit 11.247 ms, obwohl ein korrekt optimierter Plan in 12 ms fertig wäre. Faktor 937.
Was ist passiert? SQL Server hat nach dem letzten Serverneustart (vor neun Tagen) die Prozedur zum ersten Mal von Kundennummer KD-00014 aufgerufen — dem größten Kunden im System mit 1.247.392 Bestellzeilen. Für diesen Aufruf war ein Hash-Join mit Parallelismus der richtige Plan. Dieser Plan wurde gecacht.
Jetzt rufen 184 andere Kunden täglich 1.842 Mal dieselbe Prozedur auf — alle mit weniger als 200 Bestellzeilen. Für die wäre ein Index-Seek mit Nested-Loop-Join optimal. Sie bekommen trotzdem den Hash-Join-Plan für 1,2 Millionen Zeilen. Das Ergebnis ist drastisch:
|
Szenario |
Aufrufe/Stunde |
avg. CPU-Zeit |
avg. Laufzeit |
Plan-Typ |
|---|---|---|---|---|
|
KD-00014 (Großkunde) |
2 |
11.340 ms |
14.820 ms |
Hash-Join + Scan (korrekt) |
|
Andere 184 Kunden (Ist) |
1.842 |
8.403 ms |
11.247 ms |
Hash-Join + Scan (FALSCH) |
|
Andere 184 Kunden (Soll) |
1.842 |
38 ms |
12 ms |
Index-Seek + NL-Join (korrekt) |
Parameter-Sniffing-Analyse usp_GetKundenBestellungen — Ist vs. Soll
Das Ausmaß: 1.842 Aufrufe pro Stunde × (8.403 ms − 38 ms) = 15.412 CPU-Sekunden verschwendet — pro Stunde. Das sind 4,28 CPU-Stunden pro Stunde rein durch einen schlechten gecachten Plan. Dieser einzelne Befund erklärt den größten Teil der SQL-Server-CPU-Last auf SQLPROD-ERP01.
-- Parameter-Sniffing-Problem diagnostizieren
-- Min/Max-Vergleich zeigt extreme Streuung bei derselben Prozedur
SELECT TOP 10
qs.total_elapsed_time / qs.execution_count AS avg_elapsed_ms,
qs.min_elapsed_time,
qs.max_elapsed_time,
qs.max_elapsed_time / NULLIF(qs.min_elapsed_time, 0) AS faktor_minmax,
qs.execution_count,
SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset END
- qs.statement_start_offset)/2) + 1) AS sql_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qs.max_elapsed_time > qs.min_elapsed_time * 100 -- Faktor 100+ als Warnschwelle
AND qs.execution_count > 50 -- nur häufig aufgerufene Queries
ORDER BY faktor_minmax DESC;
-- Auf SQLPROD-ERP01 liefert das sofort usp_GetKundenBestellungen:
-- avg_elapsed_ms : 11.247
-- min_elapsed_time: 47 (Kleinkunde, guter Plan)
-- max_elapsed_time: 14.820 (Großkunde, Scan-Plan)
-- faktor_minmax : 315 (Faktor 315 zwischen bester und schlechtester Ausführung)
Die Lösung ist chirurgisch präzise: ein einziger Hint am Ende der Hauptabfrage in der Prozedur.
-- Vorher: usp_GetKundenBestellungen ohne Hint
-- Plan wird für den ersten Aufrufer (Großkunde KD-00014) optimiert
ALTER PROCEDURE dbo.usp_GetKundenBestellungen
@KundenNr INT,
@VonDatum DATE
AS
BEGIN
SET NOCOUNT ON;
SELECT
b.BestNr,
b.BestDatum,
b.Betrag,
k.Name
FROM dbo.Bestellungen b
JOIN dbo.Kunden k ON k.KundenNr = b.KundenNr
WHERE b.KundenNr = @KundenNr
AND b.BestDatum >= @VonDatum;
-- Kein Hint: SQL Server snifft den ersten Parameter-Wert
-- und generiert einen Plan für 1.247.392 Zeilen (Großkunde)
END;
-- Nachher: OPTIMIZE FOR UNKNOWN verhindert Sniffing
-- Der Optimizer nutzt Statistiken statt des konkreten Werts beim ersten Aufruf
ALTER PROCEDURE dbo.usp_GetKundenBestellungen
@KundenNr INT,
@VonDatum DATE
AS
BEGIN
SET NOCOUNT ON;
SELECT
b.BestNr,
b.BestDatum,
b.Betrag,
k.Name
FROM dbo.Bestellungen b
JOIN dbo.Kunden k ON k.KundenNr = b.KundenNr
WHERE b.KundenNr = @KundenNr
AND b.BestDatum >= @VonDatum
OPTION (OPTIMIZE FOR UNKNOWN); -- das ist die ganze Änderung
-- Optimizer wählt Plan basierend auf Statistiken (Median-Kunde: <200 Zeilen)
-- Ergebnis: Index-Seek statt Full Scan → avg. 12 ms statt 11.247 ms
END;
|
Hintergrund: OPTIMIZE FOR UNKNOWN — was passiert intern? |
|---|
|
Normalerweise snifft SQL Server den konkreten Parameter-Wert beim ersten Aufruf (oder nach Plan-Cache-Leerung) und optimiert den Plan für diesen Wert. |
|
OPTIMIZE FOR UNKNOWN weist den Optimizer an, stattdessen die Spaltenstatistiken zu nutzen — also den "durchschnittlichen" Wert laut Histogramm. |
|
Auf SQLPROD-ERP01: Das Histogramm auf KundenNr zeigt, dass 184 von 185 Kunden unter 200 Bestellzeilen haben. Der Optimizer wählt daher Index-Seek + Nested-Loop-Join. |
|
Nachteil: Der Großkunde KD-00014 bekommt jetzt einen suboptimalen Plan. Lösung für solche Fälle: Query Store Plan Forcing für KD-00014, OPTION(OPTIMIZE FOR UNKNOWN) für den Rest. |
|
Kapitel 18 behandelt alle Parameter-Sniffing-Lösungsstrategien im Detail. |
Befund 3: Blocking-Episode 08:52 Uhr — vollständig rekonstruiert
Um 08:52:14 Uhr beginnt die dramatischste Episode des Messzeitraums. Session 63 (der automatische Tagesabschluss-Bericht sp_GetUmsatzReport) startet einen Full Scan auf der Tabelle SalesLine — und blockiert für fast fünf Minuten jeden schreibenden Prozess im ERP-System. 14 Sessions landen in der Blocking-Chain.
Die Rekonstruktion aus dem Perfmon-Zeitreihen und DMV-Snapshots zeigt die Eskalation minutengenau:
-- Blocking-Kette diagnostizieren (während einer Episode)
-- Zeigt Head Blocker und alle blockierten Sessions
SELECT
blocking.session_id AS head_blocker,
blocked.session_id AS blocked_session,
blocked.wait_type,
blocked.wait_time / 1000.0 AS wait_sec,
blocked.status,
qt.text AS blocked_query
FROM sys.dm_exec_requests blocked
JOIN sys.dm_exec_requests blocking
ON blocked.blocking_session_id = blocking.session_id
CROSS APPLY sys.dm_exec_sql_text(blocked.sql_handle) qt
WHERE blocked.blocking_session_id > 0
ORDER BY blocked.wait_time DESC;
-- Auf SQLPROD-ERP01 um 08:53 Uhr:
-- SPID 63 ist Head Blocker (sp_GetUmsatzReport, LCK_M_S auf SalesLine)
-- SPIDs 71-84 alle mit wait_type = LCK_M_X oder LCK_M_U
-- Maximale Wartezeit: 4 Minuten 47 Sekunden
Die Ursache ist ein klassisches Reader-Writer-Konflikt-Problem: SPID 63 liest mit READ COMMITTED und hält einen Shared Lock für die gesamte Transaktionsdauer. Mit RCSI (Read Committed Snapshot Isolation) würde SPID 63 stattdessen aus einem Versions-Snapshot in TempDB lesen — kein Shared Lock, keine Blocking-Chain.
Der zweite Faktor: Der Reporting-Query macht einen Full Scan, weil kein Index auf SalesLine.OrderDate existiert. Das dauert 4 Minuten 47 Sekunden. Mit dem richtigen Index wäre der Report in unter 10 Sekunden fertig — und das Blocking-Fenster von 4:47 Minuten würde auf unter 10 Sekunden schrumpfen. Index-Anlegen und RCSI-Aktivierung sind synergistisch: Jede Maßnahme allein hilft, beide zusammen eliminieren das Problem.
|
Warnung: RCSI aktivieren: Reihenfolge beachten |
|---|
|
RCSI nutzt TempDB für die Versionierung aller geänderten Zeilen. |
|
Bevor RCSI aktiviert wird, müssen die TempDB-Dateien korrekt dimensioniert sein (Befund 1). |
|
ALTER DATABASE ErpProd SET READ_COMMITTED_SNAPSHOT ON läuft online — kein Neustart nötig. |
|
Die Aktivierung kann einige Minuten dauern, während SQL Server die bestehenden aktiven Transaktionen berücksichtigt. |
|
Während der Aktivierung kann es kurzfristig zu erhöhtem TempDB-Schreibvolumen kommen. |
-- RCSI aktivieren (online, kein Neustart erforderlich)
-- Wichtig: TempDB muss vorher korrekt dimensioniert sein (Befund 1)
ALTER DATABASE ErpProd
SET READ_COMMITTED_SNAPSHOT ON;
-- Nach Aktivierung: Reader nutzen Snapshots aus TempDB
-- LCK_M_S-Waits auf SalesLine verschwinden vollständig
-- Verifizieren:
SELECT
name,
is_read_committed_snapshot_on
FROM sys.databases
WHERE name = 'ErpProd';
-- Erwartetes Ergebnis: is_read_committed_snapshot_on = 1
Befund 4: Fehlende Indizes — die DMV sagt, was fehlt
sys.dm_db_missing_index_details ist einer der nützlichsten DMVs, die SQL Server kennt — und einer der am meisten unterschätzten. Er zeigt, welche Indizes der Query Optimizer bei der Planoptimierung als nützlich erkannt, aber nicht vorgefunden hat. Auf SQLPROD-ERP01 führt die Liste ein klarer Kandidat an:
|
Priorität |
Tabelle / Index |
Impact (DMV) |
User Seeks |
Empfohlener Index |
|---|---|---|---|---|
|
1 (KRITISCH) |
SalesLine — fehlt: (OrderDate) |
91,3 % |
847.234 |
CREATE INDEX IX_SL_OrderDate ON SalesLine(OrderDate) INCLUDE(CustomerId, TotalAmount, Status) |
|
2 (HOCH) |
PurchOrderLine — fehlt: (SupplierId, CreatedDate) |
78,4 % |
312.847 |
CREATE INDEX IX_POL_Supplier ON PurchOrderLine(SupplierId, CreatedDate) |
|
3 (MITTEL) |
InventTrans — fehlt: (ItemId, TransDate) |
65,2 % |
187.234 |
CREATE INDEX IX_IT_ItemDate ON InventTrans(ItemId, TransDate) INCLUDE(Qty, CostAmount) |
|
4 (MITTEL) |
CustLedgerEntry — fehlt: (PostingDate, CustNo) |
44,1 % |
98.412 |
CREATE INDEX IX_CLE_PostDate |
|
5 (NIEDRIG) |
VendorInvoiceJour — fehlt: (InvoiceDate) |
31,8 % |
47.831 |
CREATE INDEX IX_VIJ_InvDate |
Missing-Index-Analyse SQLPROD-ERP01 — Top-5 nach DMV-Impact
Index Priorität 1 hat einen Impact-Wert von 91,3 % — das ist der Wert, den der DMV berechnet, wenn dieser Index vorhanden gewesen wäre. 91,3 % der Query-Kosten wären damit vermeidbar gewesen. Das ist selten so klar. Und die Synergie mit dem Blocking-Problem aus Befund 3 macht diesen Index zur höchsten Priorität der gesamten Analyse.
-- Index-Priorität 1: SalesLine.OrderDate
-- ONLINE=ON: kein Ausfall, kein Blocking während der Index-Erstellung
-- MAXDOP=4: begrenzt CPU-Nutzung während des Builds (OLTP läuft parallel weiter)
-- FILLFACTOR=85: 15% Füllgrad-Reserve für Insert-Workload (AX schreibt viel)
-- SORT_IN_TEMPDB=ON: Sortierung läuft in TempDB, nicht im Data-Volume
USE ErpProd;
CREATE INDEX IX_SalesLine_OrderDate
ON dbo.SalesLine (OrderDate)
INCLUDE (CustomerId, TotalAmount, Status, ModifiedDate)
WITH (
ONLINE = ON,
MAXDOP = 4,
FILLFACTOR = 85,
SORT_IN_TEMPDB = ON
);
-- Erwartetes Ergebnis nach Index-Erstellung:
-- sp_GetUmsatzReport: von 4 Min 47 Sek auf < 10 Sek
-- Blocking-Fenster: von 4:47 Min auf < 10 Sek
-- Full Scans/sec in Perfmon: von avg. 8,3 auf < 1
|
Hinweis: DMV-Impact ist nicht Echtzeit, sondern kumulativ |
|---|
|
sys.dm_db_missing_index_details und der zugehörige Impact-Wert werden seit dem letzten SQL Server-Neustart kumuliert. |
|
Ein Impact von 91,3 % bedeutet nicht, dass 91 % der aktuellen Last entfällt — es bedeutet, dass 91 % der bisherigen Query-Kosten (seit Neustart vor 9 Tagen) durch diesen Index vermeidbar gewesen wären. |
|
Der DMV nennt auch keine Kosten für das Index-Pflegen (mehr Platz, langsamere INSERTs/UPDATEs auf SalesLine). |
|
Vor dem Anlegen jedes Index: Was ist die Schreiblast auf der Tabelle? FILLFACTOR entsprechend wählen. |
|
Die Indizes 2–5 werden nach dem Rollout von Index 1 neu bewertet — manchmal löst ein Index das Muster anderer DMV-Empfehlungen auf. |
Befund 5: Konfigurationsdetails — die schnellen Gewinne
Neben den vier großen Befunden gibt es drei Konfigurationsdetails, die in zehn Minuten erledigt sind und trotzdem messbare Wirkung haben. Cost Threshold for Parallelism, Optimize for Ad Hoc Workloads und Query Store — alle drei zusammen kosten weniger Aufwand als eine Tasse Kaffee holen.
-- Konfigurationsanpassungen: schnelle Gewinne ohne Neustart
-- Cost Threshold von 25 auf 50 erhöhen
-- Verhindert unnötige Parallelisierung einfacher OLTP-Queries
-- Wirkung: CXPACKET-Waits sinken, weniger Scheduler-Druck
EXEC sp_configure 'cost threshold for parallelism', 50;
RECONFIGURE;
-- Optimize for Ad Hoc Workloads aktivieren
-- Monitoring-Agent erzeugt hunderte einmalige Ad-hoc-Queries pro Stunde
-- Mit diesem Setting: erste Ausführung wird als "stub" gecacht (klein)
-- Erst bei zweiter Ausführung kommt der volle Plan-Cache-Eintrag
-- Wirkung: Plan-Cache-Druck sinkt, Compilation-Rate sinkt
EXEC sp_configure 'optimize for ad hoc workloads', 1;
RECONFIGURE;
-- Query Store aktivieren (Planstabilität und Diagnose-Historie)
-- Wichtig nach Parameter-Sniffing-Fix: Query Store überwacht Plan-Regression
ALTER DATABASE ErpProd
SET QUERY_STORE = ON
(OPERATION_MODE = READ_WRITE,
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),
QUERY_CAPTURE_MODE = AUTO,
MAX_STORAGE_SIZE_MB = 1024);
-- Kapitel 19 erklärt Query Store und Plan Forcing im Detail
Cost Threshold for Parallelism von 25 auf 50 klingt wie ein kleiner Schritt. Auf einem ERP-System mit kurzen OLTP-Queries ist er das nicht: Queries mit geschätzten Kosten zwischen 25 und 50 werden ab jetzt seriell ausgeführt, statt alle 8 parallelen Threads zu beanspruchen. Der Effekt: CXPACKET-Waits sinken, die Anmeldewelle am Morgen wird gleichmäßiger abgearbeitet.
Query Store ist nicht nur ein nettes Feature — es ist die Versicherung für den Parameter-Sniffing-Fix. Wenn nach dem nächsten Neustart ein neuer schlechter Plan für usp_GetKundenBestellungen gecacht wird (was mit OPTIMIZE FOR UNKNOWN weniger wahrscheinlich, aber nicht unmöglich ist), erkennt Query Store die Plan-Regression automatisch. Kapitel 19 zeigt, wie Plan Forcing in diesem Fall funktioniert.
Wait Statistics: Das vollständige Bild
Die kumulativen Wait Statistics der neun Tage seit dem letzten Neustart bestätigen alle fünf Befunde und liefern die Priorisierungsgrundlage. Wie Kapitel 9 erklärt: Wait Statistics sind der Kompass, nicht die Karte. Sie zeigen, wo zu suchen ist — die Ursache muss man danach selbst finden.
|
Wait Type |
Tasks (Mio.) |
Wartezeit (s) |
Ursache |
Maßnahme |
|---|---|---|---|---|
|
PAGELATCH_EX |
84 |
3.824 |
TempDB: 4 Dateien für 16 Kerne |
12 TempDB-Dateien hinzufügen |
|
LCK_M_S |
31 |
2.187 |
Kein RCSI — Report blockiert 14 Sessions |
RCSI aktivieren |
|
CXPACKET |
52 |
1.204 |
Cost Threshold = 25 parallelisiert zu viele OLTP-Queries |
Cost Threshold auf 50 |
|
PAGEIOLATCH_SH |
18 |
394 |
Full Scans wegen fehlendem Index SalesLine.OrderDate |
Index anlegen |
|
SOS_SCHEDULER_YIELD |
9 |
287 |
CPU-intensive Scans + Parameter-Sniffing-Plan |
Parameter Sniffing beheben |
|
ASYNC_NETWORK_IO |
3 |
48 |
AX verarbeitet große Resultsets sequenziell |
App-seitig: Pagination |
|
WRITELOG |
2 |
14 |
Log-Flush bei COMMIT — normal für diese Last |
Kein Handlungsbedarf |
|
MEMORY_ALLOCATION_EXT |
1 |
8 |
Geringes OS-Memory-Druck-Signal |
PLE-Monitoring etablieren |
Wait Statistics Top-8 SQLPROD-ERP01 (kumulativ, 9 Tage seit Neustart)
Auffällig: WRITELOG mit 14 Sekunden Gesamtwartezeit ist verschwindend gering im Vergleich zu PAGELATCH_EX (3.824 s) und LCK_M_S (2.187 s). Der Log-IO auf Volume E: ist also kein Problem — eine Information, die ohne Wait Statistics leicht falsch eingeschätzt würde ("das Log-Volume ist voll, das muss das Problem sein").
ASYNC_NETWORK_IO mit 48 Sekunden ist interessant: SQL Server hat Ergebnismengen fertig berechnet, aber der AX-Client holt sie nicht schnell genug ab. Das ist ein Applikationsproblem — die AX-Middleware verarbeitet große Result-Sets sequenziell. Hier liegt kein DBA-Problem vor; das Trendforge-Kapitel (Kapitel 34) zeigt, wie solche Applikationsprobleme in einer anderen Konstellation eskalieren können.
Diagnose-Kästen: Das Wichtigste auf einen Blick
|
Warnung: Symptome — woran du dieses Muster erkennst |
|---|
|
Nutzer berichten: Morgens zwischen 07:45 und 08:15 Uhr ist das System merklich langsamer. |
|
Nutzer berichten: Bestimmte Berichte hängen und blockieren andere — manchmal minutenlang. |
|
Nutzer berichten: Buchungsvorgänge dauern manchmal 5× länger als gewöhnlich, ohne Muster. |
|
Im Monitoring: PAGELATCH_EX dominiert die Wait Statistics (>20% Anteil) — TempDB-Signal. |
|
Im Monitoring: LCK_M_S mit hoher Wartezeit (>5% Anteil) — Reader-Writer-Konflikt. |
|
Im Error Log: Kein Hinweis auf Fehler — der Server fühlt sich selbst nicht krank. |
|
Tipp: So misst du das — die drei wichtigsten Abfragen |
|---|
|
1. Wait Statistics Top-10: SELECT TOP 10 wait_type, waiting_tasks_count, wait_time_ms FROM sys.dm_os_wait_stats WHERE wait_type NOT IN (<idle waits>) ORDER BY wait_time_ms DESC |
|
2. Parameter Sniffing: sys.dm_exec_query_stats mit min/max_elapsed_time — Faktor > 100 als Warnschwelle, Kapitel 18 zeigt die vollständige Diagnose-Abfrage. |
|
3. Blocking: sys.dm_exec_requests WHERE blocking_session_id > 0 — Head Blocker identifizieren, Lock-Typ und Wartezeit auslesen. |
|
Für alle drei: Kapitel 9 zeigt den vollständigen Diagnose-Workflow mit Interpretation. |
|
Hintergrund: Typische Fehlinterpretationen bei diesem Muster |
|---|
|
"Der Server ist zu schwach" — Nein. Hardware war in Ordnung: 16 vCPUs, 64 GB RAM, exzellente IO-Latenzen. Die Konfiguration war das Problem. |
|
"Es liegt an den Reporting-Queries" — Teilweise richtig, aber zu kurz gedacht: Der Report blockierte, weil RCSI fehlte, und dauerte zu lange, weil ein Index fehlte. Beide Ursachen sind fixbar. |
|
"CXPACKET ist das Hauptproblem" — Nein. CXPACKET war Symptom (Cost Threshold zu niedrig), nicht Hauptursache. TempDB-Contention und Parameter Sniffing waren teurer. |
|
"Hoher PLE bedeutet: alles ok mit Memory" — Grundsätzlich richtig, aber Memory Grants Pending im Maximum 7 ist ein Frühwarnsignal für zukünftigen Druck. |
|
"Kein IO-Problem laut Perfmon" — Stimmt für Latenzen. TempDB-Contention ist ein Latch-Problem, kein IO-Problem — Perfmon IO-Latenzen zeigen das nicht. |
|
Tipp: Erste Gegenmaßnahmen — Triage, bevor die Analyse abgeschlossen ist |
|---|
|
1. TempDB-Dateianzahl erhöhen: ALTER DATABASE tempdb ADD FILE (…) — dauert 15 Minuten, kein Neustart, sofortige Wirkung auf PAGELATCH_EX. |
|
2. Cost Threshold auf 50: EXEC sp_configure "cost threshold for parallelism", 50; RECONFIGURE — dauert 30 Sekunden, reduziert CXPACKET-Last sofort. |
|
3. Optimize for Ad Hoc Workloads: EXEC sp_configure "optimize for ad hoc workloads", 1; RECONFIGURE — sofortige Wirkung auf Compilation-Rate. |
|
4. Missing Index Priorität 1 anlegen (ONLINE=ON): Kein Ausfall, reduziert Full Scans und verkürzt das Blocking-Fenster dramatisch. |
|
5. RCSI erst nach TempDB-Fix aktivieren: RCSI braucht TempDB für Versioning — falsche Reihenfolge verschlimmert die Contention. |
Roadmap 0–60 Tage: Was wann, von wem, mit welchem Effekt
Die Analyse liefert nicht nur Befunde, sondern eine priorisierte Roadmap. Die Reihenfolge ist wichtig: Schritt 1 ist Voraussetzung für Schritt 5 (RCSI braucht dimensionierte TempDB). Schritt 3 schafft die Monitoring-Grundlage, die nach Schritt 2 und 4 die Verbesserung messbar macht.
|
Woche |
Priorität |
Maßnahme |
Aufwand |
Risiko |
Verifikation |
|---|---|---|---|---|---|
|
W1 |
1 |
TempDB: 12 Datendateien hinzufügen (Skript 5.1) |
15 Min |
GERING |
PAGELATCH_EX sinkt >75 % |
|
W1 |
2 |
Parameter Sniffing: OPTIMIZE FOR UNKNOWN in Prozedur |
30 Min |
GERING |
avg Query-Zeit < 100 ms |
|
W1 |
3 |
Cost Threshold = 50 + Ad Hoc Workloads = ON |
5 Min |
GERING |
Compilations/sec < 0,5 % |
|
W2 |
4 |
Index IX_SalesLine_OrderDate anlegen (ONLINE=ON) |
20 Min |
MITTEL |
Full Scans/sec < 1, Report < 10 s |
|
W2 |
5 |
RCSI auf ErpProd aktivieren (nach TempDB-Fix) |
1 Std. |
MITTEL |
LCK_M_S-Waits verschwinden |
|
W3 |
6 |
Query Store aktivieren (ErpProd) |
30 Min |
GERING |
Planstabilität sichtbar |
|
W4 |
7 |
Statistiken auf ErpArch aktualisieren (47 Tage alt) |
1 Std. |
GERING |
Letzte Aktualisierung < 7 Tage |
|
W5 |
8 |
Indizes Priorität 2–5 anlegen (ONLINE=ON) |
3 Std. |
MITTEL |
CPU-Top-Queries überprüfen |
|
M2 |
9 |
Index-Wartung einrichten (Ola Hallengren) |
2 Std. |
GERING |
Wartungs-Job läuft wöchentlich |
|
M2 |
10 |
Monitoring-Baseline etablieren (PLE, Waits, Disk) |
4 Std. |
GERING |
Dashboard + Alerting aktiv |
Roadmap SQLPROD-ERP01 — 0 bis 60 Tage (sortiert nach Priorität)
Woche 1 ist die wichtigste. Die drei Maßnahmen in Woche 1 kosten zusammen weniger als eine Stunde und eliminieren die primären Performance-Killer: TempDB-Contention, Parameter Sniffing und unnötige Parallelisierung. Alles ohne Neustart, alles ohne Downtime. Das sind genau die schnellen Gewinne, die das Vertrauen der Nutzer zurückgewinnen — während die tieferen Maßnahmen (Index, RCSI) in Woche 2 sauber geplant und umgesetzt werden.
Schritt 10 ist in einer Analyse-Roadmap ungewöhnlich, aber hier zwingend: SQLPROD-ERP01 hatte kein Monitoring. Ohne Monitoring gibt es keine Baseline, ohne Baseline gibt es keine Verifikation, und ohne Verifikation weiß man nach dem nächsten Incident nicht, ob sich etwas verbessert oder verschlechtert hat. Kapitel 31 beschreibt Collect-SqlPerf.ps1 als praktisches Werkzeug für genau diesen Zweck.
Vorher/Nachher: Was die Zahlen sagen
Vier Wochen nach der Analyse und Umsetzung aller Woche-1- und Woche-2-Maßnahmen zeigt ein erneuter Messpunkt die Wirkung. Die Werte sind nicht geschätzt — sie kommen aus dem neu eingerichteten Monitoring-Dashboard:
|
Kennzahl |
Vorher |
Nachher |
Verbesserung |
|---|---|---|---|
|
PAGELATCH_EX (kumulativ/Tag) |
425 s / Tag |
11 s / Tag |
−97 % |
|
LCK_M_S (kumulativ/Tag) |
243 s / Tag |
< 1 s / Tag |
−99 % |
|
avg. Laufzeit usp_GetKundenBestellungen |
11.247 ms |
38 ms |
−99,7 % |
|
Dauer Tagesabschluss-Bericht |
4 Min 47 Sek |
8 Sek |
−97 % |
|
SQL Compilations/sec (avg) |
1,4 / sec |
0,3 / sec |
−79 % |
|
CPU-Auslastung (sqlservr avg) |
112,7 % |
41,2 % |
−63 % |
|
Memory Grants Pending (max) |
7 |
1 |
−86 % |
|
Nutzer-Beschwerden Morgen-Peak |
täglich |
keine |
−100 % |
Vorher/Nachher-Vergleich SQLPROD-ERP01 — vier Wochen nach Umsetzung
Die dramatischste Verbesserung ist usp_GetKundenBestellungen: von 11.247 ms auf 38 ms. Das ist nicht magie — das ist ein einziger Query-Hint (OPTIMIZE FOR UNKNOWN) der in 30 Minuten implementiert war. Die gesamte CPU-Auslastung sinkt von durchschnittlich 112 % auf 41 % — mehr als halbiert, ohne Hardware-Änderung.
Der Tagesabschluss-Bericht läuft jetzt in 8 Sekunden statt in 4 Minuten 47 Sekunden. Das Blocking-Fenster ist von fast fünf Minuten auf nicht mehr messbar gesunken: RCSI sorgt dafür, dass der Bericht ohne Shared Locks liest, und der neue Index sorgt dafür, dass er überhaupt schnell fertig wird.
Zusammenfassung: Was dieser Fall lehrt
Die Musterwerk GmbH ist kein Einzelfall. Sie ist der Normalfall. Solide Hardware, kein offensichtlicher Fehler, ein kompetenter Administrator — und trotzdem 97 % verschwendete CPU-Zeit in der meistgenutzten Prozedur, 5 Minuten Blocking täglich durch ein fehlendes Feature und Allokierungs-Contention durch eine falsche Zahl in einer Konfigurationsoption.
Die wichtigsten Erkenntnisse aus dieser Fallstudie:
Was dieser Fall auch zeigt: Die Grenze zwischen "normal" und "optimal" liegt bei diesem Server bei 63 % CPU-Last — die einfach verschwendet wurde. Multipliziert mit den Serverkosten, den Lizenzkosten und dem Zeitaufwand der Nutzer, die täglich auf Queries warten, ist das kein akademisches Problem. Es ist ein Kostenfaktor.
Wer nach dieser Fallstudie das Bedürfnis verspürt, die eigene Installation zu überprüfen, sei auf Kapitel 31 verwiesen: Das Phasenmodell und das Collect-SqlPerf.ps1-Skript ermöglichen genau diese Analyse in strukturierter Form. Und, wenn dein Ergebnis der Musterwerk GmbH ähnelt — gratuliere. Das bedeutet, du hast Potenzial.
|
Hinweis: Musterwerk GmbH in anderen Kapiteln |
|---|
|
Kapitel 9 (Wait Statistics): PAGELATCH_EX als TempDB-Signal — das Muster, das hier gefunden wurde. |
|
Kapitel 13 (TempDB): TempDB-Dimensionierung, Proportional Fill, Allokierungs-Contention im Detail. |
|
Kapitel 14 (Blocking und Deadlocks): Reader-Writer-Konflikte und RCSI als strukturelle Lösung. |
|
Kapitel 18 (Parameter Sniffing): OPTIMIZE FOR UNKNOWN vs. andere Lösungsstrategien — Kapitel 18 zeigt, wann welche Lösung passt. |
|
Kapitel 31 (Analyse-Methodik): Das Phasenmodell, das dieser Analyse zugrunde lag. |
Ausblick: Kapitel 33 — wenn "normal" wie ein Urlaub wirkt
Die Musterwerk GmbH hat ein paar Konfigurationsfehler und fehlende Indizes — überschaubar, lösbar, lehrreich. Was Kapitel 33 zeigt, ist eine andere Kategorie. Sparfuchs & Partner Steuerberatungs GmbH hat nicht ein paar Probleme. Sie hat alle Probleme. Gleichzeitig. Auf einem System.
847 Autogrowth-Events in 120 Minuten. 48.312 VLFs. 183 GB Transaktionslog bei 24 GB Datenbank. max server memory = 8.192 MB auf einem System mit 8 GB physischem RAM. Priority Boost aktiviert. Kompatibilitätslevel 110 (SQL Server 2012) auf SQL Server 2019.
Nach Kapitel 32 ist Kapitel 33 kein Schockmoment mehr — sondern eine strukturierte Analyse, die zeigt, was passiert, wenn man über Jahre hinweg keine einzige Performance-Grundlage beachtet. Für Administratoren, die ihren eigenen Server nach diesem Kapitel überprüfen möchten: Der Vergleich mit Sparfuchs & Partner wird entspannend wirken.

Abb. 1: Wait Statistics: Musterwerk GmbH Ausgangssituation

Abb. 2: Musterwerk GmbH: Verbesserungen im Überblick
Kapitel 33
