Datenbankeinstellungen:
Auto-Close, Auto-Shrink und andere Selbstzerstörungsoptionen
6.1 Datenbankebene vs. Serverebene: Verschiedene Schrauben
In Kapitel 5 haben wir die Serverkonfiguration durchleuchtet — die Einstellungen, die für alle Datenbanken einer Instanz gelten. Jetzt gehen wir eine Ebene tiefer: jede Datenbank hat ihren eigenen Satz an Optionen, und manche davon sind auf "schädlich" voreingestellt. Das ist kein Witz, das ist Legacy.
Die gute Nachricht: SQL Server ist da transparent. Mit ALTER DATABASE … SET … lassen sich alle relevanten Optionen sauber konfigurieren. Die schlechte Nachricht: Niemand tut das. Neue Datenbanken erben ihre Einstellungen von der model-Datenbank — und, wenn model schlechte Einstellungen hat, bekommt jede neue Datenbank schlechte Einstellungen. Automatisch. Still. Ohne Warnung.
In diesem Kapitel gehen wir die wichtigsten Datenbankoptionen durch: Recovery Model, Auto-Close, Auto-Shrink, RCSI, Compatibility Level, Database Scoped Configurations, Virtual Log Files, Statistiken, Page Verify und die oft vergessene model-Datenbank. Am Ende weißt du, was du nach der Datenbankerstellung als Erstes prüfen solltest — und warum du die model-Datenbank nicht ignorieren darfst.
Viele der hier beschriebenen Probleme tauchen in den Fallstudien von Teil V wieder auf. Sparfuchs & Partner (Kapitel 33) hat fast jeden Fehler aus diesem Kapitel gleichzeitig begangen — ein lehrreiches Beispiel, wenn auch kein beneidenswertes. Musterwerk GmbH (Kapitel 32) zeigt, wie ein Konfigurationsaudit in einem Nachmittag die schlimmsten Probleme beseitigt.
6.2 Recovery Model: Die wichtigste Datenbankentscheidung
Das Recovery Model bestimmt, wie SQL Server das Transaktionslog behandelt. Es gibt drei Varianten: SIMPLE, FULL und BULK-LOGGED. Wer diese Entscheidung falsch trifft, riskiert entweder Datenverlust oder einen Transaktionslog, der das Laufwerk auffrisst. Beides ist unangenehm, beides passiert täglich irgendwo da draußen.
Das Transaktionslog ist nicht nur Protokoll — es ist die Grundlage für Durability im ACID-Sinne. Jede Transaktion landet zuerst im Log (Write-Ahead Logging, kurz WAL), bevor die Änderungen in die eigentlichen Datenseiten geschrieben werden. Kapitel 4 erklärt das WAL-Prinzip im Detail; hier interessiert uns vor allem die Frage: Wie lange muss das Log aufbewahrt werden?
SIMPLE — weniger ist manchmal mehr
Im SIMPLE-Modell kürzt SQL Server das Transaktionslog automatisch bei jedem Checkpoint. Das bedeutet: der Log bleibt klein, aber du kannst nicht auf einen beliebigen Zeitpunkt wiederherstellen (Point-in-Time Recovery). Du kannst nur auf den letzten Full-Backup oder Differential-Backup zurück. Für Entwicklungs- und Testdatenbanken ist das vollkommen in Ordnung. Für Produktion fast immer falsch.
SIMPLE ist außerdem der richtige Kandidat für Data Warehouses mit strikten ETL-Fenstern, wo ein Full-Backup nach jedem Ladezyklus sowieso Standard ist. Der Log wächst nie aus dem Ruder, weil Checkpoint ihn regelmäßig freigibt — kein Log-Backup-Job nötig.
FULL — der Standard für Produktion
Im FULL-Modell wird jede einzelne Transaktion vollständig im Log protokolliert. Das ermöglicht Point-in-Time Recovery — du kannst die Datenbank auf den Stand von vor genau 14:23:07 Uhr zurückbringen, falls nötig. Der Preis: das Transaktionslog wächst und wächst, bis ein Log-Backup gemacht wird. Ohne regelmäßige Log-Backups läuft das Laufwerk voll. Klingt trivial, passiert aber erschreckend oft — und dann steht die Produktion still.
Die Columne log_reuse_wait_desc in sys.databases zeigt genau, warum ein Log gerade nicht truncated werden kann. Häufigste Werte: LOG_BACKUP (kein Backup gemacht), ACTIVE_TRANSACTION (offene Transaktion blockiert), DATABASE_MIRRORING oder AVAILABILITY_REPLICA (High-Availability-Infrastruktur wartet auf Log-Versand). Dieser Wert ist bei einem Log-Wachstumsproblem immer der erste Anlaufpunkt.
|
Warnung: FULL ohne Log-Backup ist schlimmer als SIMPLE |
|---|
|
Ein häufiger Fehler: FULL eingestellt, aber kein Log-Backup-Job konfiguriert. Das Ergebnis ist das Schlechteste aus beiden Welten: kein Point-in-Time Recovery (weil niemand die Logs sichert) und ein Log, das stetig wächst. Sparfuchs & Partner in Kapitel 33 hatte dieses Problem in Extremform: 183 GB Transaktionslog bei 24 GB Datenbankgröße. Letztes Log-Backup: nie. log_reuse_wait_desc = LOG_BACKUP seit dem initialen Aufsetzen der Instanz. |
BULK-LOGGED — der Kompromiss für Massenoperationen
BULK-LOGGED ist ein Zwischenweg: Bulk-Operationen (BULK INSERT, SELECT INTO, bcp, index rebuild) werden minimal geloggt und sparen so Schreiblast und Log-Platzbedarf. Dafür schränkt sich die Point-in-Time Recovery für den Zeitraum der Bulk-Operation ein. Das Modell macht Sinn für nächtliche ETL-Prozesse oder große Datenmigratonen — aber nur in Kombination mit einem anschließenden Log-Backup, das die Bulk-Änderungen sichert.
Wichtig: BULK-LOGGED ist kein Dauerzustand. Das typische Muster ist: vor dem ETL-Fenster auf BULK-LOGGED wechseln, ETL durchführen, Log-Backup machen, zurück auf FULL. Wer BULK-LOGGED dauerhaft aktiviert lässt, riskiert überraschend große Log-Backups, wenn Bulk-Änderungen die gesamten Extents sichern müssen.
|
Kriterium |
SIMPLE |
FULL |
BULK-LOGGED |
|---|---|---|---|
|
Log-Truncation |
Automatisch bei Checkpoint |
Nur nach Log-Backup |
Nur nach Log-Backup |
|
Point-in-Time Recovery |
Nicht möglich |
Vollständig möglich |
Eingeschränkt (Bulk-Phasen) |
|
Log-Backup erforderlich |
Nein |
Ja — zwingend! |
Ja — zwingend! |
|
Log-Wachstum unkontrolliert |
Nein |
Ohne Backup: ja |
Ohne Backup: ja |
|
Bulk-Ops minimal geloggt |
Ja |
Nein (voll geloggt) |
Ja |
|
Typischer Einsatz |
Dev/Test, DWH |
OLTP-Produktion |
ETL mit anschl. Log-Backup |
Tab. 6.1: Recovery Model Vergleich

Abb. 6.1: Recovery Models im Vergleich
Recovery Model setzen oder prüfen mit:
-- Aktuelles Recovery Model aller Datenbanken anzeigen
-- Damit siehst du auf einen Blick, wo noch Handlungsbedarf besteht
SELECT name,
recovery_model_desc,
log_reuse_wait_desc -- warum der Log gerade nicht truncaten kann
FROM sys.databases
ORDER BY name;
-- Recovery Model auf FULL setzen
-- Danach sofort einen Full-Backup machen, damit die Log-Kette beginnt!
ALTER DATABASE MeineDatenbank SET RECOVERY FULL;
|
Tipp: Log-Kette nach Modellwechsel |
|---|
|
Wer von SIMPLE auf FULL wechselt, muss danach sofort einen Full-Backup durchführen. Erst dann beginnt die Log-Kette, und erst dann sind Log-Backups und Point-in-Time Recovery möglich. Ohne diesen Backup bleibt der log_reuse_wait_desc auf LOG_BACKUP — der Log wächst, aber nützt nichts. |
6.3 Auto-Close: Das Anti-Pattern schlechthin
Auto-Close klingt nach einer cleveren Sparmaßnahme: wenn niemand auf die Datenbank zugreift, wird sie geschlossen — Ressourcen werden freigegeben. Beim nächsten Zugriff öffnet SQL Server sie wieder. Klingt effizient. Ist es nicht.
Das Problem liegt im "Öffnen": Wenn SQL Server eine Datenbank öffnet, muss er den Recovery-Prozess durchlaufen, Statistiken laden, den Buffer Pool befüllen. Das kostet zwischen 100 ms und mehreren Sekunden — je nach Datenbankgröße. Für den Nutzer, der als Erster auf die Datenbank zugreift, fühlt sich das wie eine lahme Applikation an. Für den Monitoring-Job, der alle paar Sekunden Verbindungen öffnet und schließt, ist es ein Performance-Albtraum: ständiges Öffnen und Schließen, ständige Recovery-Zyklen.
Besonders perfide: Auto-Close verhindert, dass der Buffer Pool warm bleibt. Jedes Öffnen der Datenbank beginnt mit einem leeren Cache — alle Daten müssen erneut vom Disk gelesen werden. Das erhöht die IO-Last und verlängert die Antwortzeiten für die ersten Abfragen nach jeder "Schließung". In Kapitel 11 zur Memory-Verwaltung wird deutlich, wie teuer ein kalter Buffer Pool wirklich ist.
-- Auto-Close-Status aller Datenbanken prüfen
-- is_auto_close_on = 1 bedeutet: Problem vorhanden
SELECT name, is_auto_close_on
FROM sys.databases
WHERE is_auto_close_on = 1;
-- Auto-Close deaktivieren
-- Keine Nebeneffekte außer: die DB bleibt jetzt dauerhaft geöffnet (so soll es sein)
ALTER DATABASE MeineDatenbank SET AUTO_CLOSE OFF;
|
Hinweis: Woher kommt Auto-Close = ON überhaupt? |
|---|
|
In SQL Server Express und LocalDB ist Auto-Close standardmäßig aktiviert — das macht für Desktop-Anwendungen mit gelegentlichem Zugriff Sinn. Das Problem entsteht, wenn Express-Datenbanken in Produktionsumgebungen landen oder, wenn die model-Datenbank die Einstellung erbt. Prüfe model explizit: ALTER DATABASE model SET AUTO_CLOSE OFF; |
6.4 Auto-Shrink: Das Paradox der selbstverschlimmernden Lösung
Auto-Shrink ist die eleganteste Art, sich selbst in die Knie zu schießen. Die Idee: SQL Server überprüft regelmäßig, ob in einer Datenbankdatei ungenutzter Speicherplatz vorhanden ist, und gibt ihn ans Betriebssystem zurück. Klingt vernünftig — ist aber ein Desaster.
Das Paradox entfaltet sich so: Auto-Shrink verkleinert die Datenbankdatei. Beim nächsten Datenwachstum muss SQL Server via Autogrowth wieder expandieren. Jedes Autogrowth-Ereignis verursacht IO-Spikes und erzeugt neue VLFs (bei Logfiles). Außerdem muss Shrink die Daten physisch verschieben, was zu massiver Index-Fragmentierung führt. Nach dem Shrink hat man also: weniger freien Platz (temporär), mehr Fragmentierung, schlechtere Performance. Und kurz darauf wieder eine größere Datei, weil die Daten zurückgewachsen sind.
Die Index-Fragmentierung durch Auto-Shrink ist keine theoretische Bedrohung: SHRINK verschiebt Seiten von hinten nach vorne, ohne Rücksicht auf logische Sortierung. Ein Index, der vorher linear sortiert auf Disk lag, ist danach wild verstreut. Das trifft Range Scans besonders hart — sie müssen plötzlich deutlich mehr IO erzeugen für dieselbe Datenmenge. Mehr zur Index-Fragmentierung und ihrer Behebung in Kapitel 17.
Dieser Kreislauf erzeugt kontinuierliche Hintergrund-Last ohne jeden Nutzen. Es ist wie jeden Morgen das Zimmer aufräumen und am Abend alles wieder durcheinander werfen — mit dem Unterschied, dass jede Runde die Möbel ein bisschen mehr zerkratzt. Und in Kapitel 10 zur IO-Performance wirst du sehen, wie Auto-Shrink auf IO-Ebene aussieht: wie ein konstantes Rauschen von wahlfreien Schreibzugriffen, das nie aufhört.
-- Auto-Shrink-Status aller Datenbanken prüfen
-- is_auto_shrink_on = 1 ist immer ein Problem — keine Ausnahmen
SELECT name, is_auto_shrink_on
FROM sys.databases
WHERE is_auto_shrink_on = 1;
-- Auto-Shrink deaktivieren
ALTER DATABASE MeineDatenbank SET AUTO_SHRINK OFF;
|
Warnung: Manuelles SHRINK ist auch keine Lösung |
|---|
|
Auch manuelles DBCC SHRINKFILE sollte die absolute Ausnahme sein. Wenn wirklich dauerhaft zu viel Platz belegt ist (nach einer großen Löschaktion), ist ein Shrink einmalig tolerierbar — aber nur gefolgt von sofortigem Index-Rebuild. In aller Regel ist die bessere Lösung: mehr Speicherplatz bereitstellen. Disk ist günstig. Fragmentierungs-Schäden nicht. |
6.5 Read Committed Snapshot Isolation (RCSI): Der Gamechanger für Blocking
Blocking ist eines der häufigsten Performance-Probleme in OLTP-Datenbanken. Die Ursache ist simpel: SQL Server verwendet standardmäßig pessimistisches Locking. Ein Leser blockiert Schreiber und umgekehrt. Kapitel 14 behandelt Blocking und Deadlocks ausführlich — hier geht es um eine Datenbankoption, die dieses Problem für die überwiegende Mehrheit der Fälle elegant löst: Read Committed Snapshot Isolation, kurz RCSI.
Was ist RCSI?
RCSI ist eine Datenbankoption, die das Verhalten des Standard-Isolationslevels READ COMMITTED verändert. Ohne RCSI gilt: ein Leser wartet, wenn ein Schreiber die angefragte Seite sperrt. Mit RCSI gilt: der Leser bekommt die letzte committete Version der Zeile — ohne zu warten, ohne die aktive Transaktion des Schreibers zu stören. Leser blockieren keine Schreiber, Schreiber blockieren keine Leser.
Das klingt nach Magie, ist aber Technik: SQL Server pflegt in TempDB eine Versionszeigerverkette. Wenn eine Transaktion eine Zeile ändert, legt SQL Server die alte Version der Zeile als Zeilenversion (Row Version) in TempDB ab und hängt einen 14-Byte-Zeiger ans Ende der Original-zeile. Lesende Transaktionen, die eine ältere committete Version benötigen, folgen diesem Zeiger und lesen aus dem Versionsspeicher — ohne die aktive Änderung zu sehen und ohne auf sie warten zu müssen.
|
Definition: Row Versioning |
|---|
|
Beim Row Versioning speichert SQL Server alte Zeilenversionen in TempDB. Jeder Eintrag im Versionsspeicher enthält: die geänderten Spaltenwerte, den Transaktions-Sequenz-Stamp (XSN) und einen Zeiger auf die nächst ältere Version (falls vorhanden). Lesende Transaktionen navigieren diese Kette bis sie die für sie gültige Version gefunden haben. |
Auswirkungen auf TempDB
Row Versioning ist kein Freifahrtschein — es kostet TempDB-Platz und -Last. Jede geänderte Zeile erzeugt mindestens einen Versionseintrag in TempDB, der so lange aufbewahrt wird, bis keine aktive Transaktion mehr eine ältere Version benötigt. Bei langen Transaktionen oder vielen parallelen Schreibvorgängen kann der Versionsspeicher beträchtlich wachsen — in Extremfällen auf mehrere Gigabyte.
Das bedeutet: TempDB muss entsprechend dimensioniert sein, bevor RCSI aktiviert wird. Mehr zu TempDB-Dimensionierung und Monitoring des Versionsspeichers in Kapitel 13. Kapitel 29 behandelt Row Versioning und Snapshot Isolation vertieft — inklusive der Unterschiede zwischen RCSI und dem höheren Snapshot-Isolationslevel.
RCSI vs. Snapshot Isolation
RCSI und Snapshot Isolation (SI) sind verwandt, aber nicht gleich. RCSI verändert das Verhalten des Standard-Isolationslevels READ COMMITTED still und transparent — Anwendungen merken nichts, profitieren aber sofort. SI hingegen ist ein expliziter Isolationslevel, den Transaktionen anfordern müssen (SET TRANSACTION ISOLATION LEVEL SNAPSHOT). SI bietet stärkere Garantien (konsistente Sicht auf den Datenbankzustand zu Transaktionsbeginn), kostet aber mehr im Versionsspeicher und erfordert Anpassungen im Anwendungscode.
|
Eigenschaft |
READ COMMITTED (Standard) |
RCSI (READ_COMMITTED_SNAPSHOT ON) |
Snapshot Isolation |
|---|---|---|---|
|
Leser blockiert Schreiber |
Ja |
Nein |
Nein |
|
Schreiber blockiert Leser |
Ja |
Nein |
Nein |
|
Schreiber blockiert Schreiber |
Ja |
Ja (Zeilen-Lock) |
Ja (Update-Konflikt) |
|
TempDB-Versionsspeicher |
Nein |
Ja |
Ja |
|
Anwendungsänderung nötig |
Nein |
Nein |
Ja (SET ISOLATION LEVEL) |
|
Konsistenz der Lesung |
Statement-konsistent |
Statement-konsistent |
Transaktions-konsistent |
|
Typischer Einsatz |
OLTP (legacy) |
OLTP (moderner Standard) |
Reporting auf OLTP |
Tab. 6.2: Isolation Level im Vergleich
RCSI aktivieren — das richtige Vorgehen
Das Aktivieren von RCSI ist technisch einfach, muss aber mit Bedacht geschehen. Die ALTER DATABASE-Anweisung erfordert exklusiven Zugriff auf die Datenbank — alle aktiven Verbindungen müssen vorher geschlossen werden. In Produktionsumgebungen ist ein Wartungsfenster daher Pflicht.
-- Schritt 1: Aktuellen Status prüfen
-- is_read_committed_snapshot_on = 1 bedeutet: RCSI ist aktiv
SELECT name,
is_read_committed_snapshot_on,
snapshot_isolation_state_desc
FROM sys.databases
WHERE name = 'MeineDatenbank';
-- Schritt 2: RCSI aktivieren (erfordert exklusiven Zugriff)
-- ROLLBACK AFTER 60 bricht offene Transaktionen nach 60 Sekunden ab
ALTER DATABASE MeineDatenbank
SET READ_COMMITTED_SNAPSHOT ON
WITH ROLLBACK AFTER 60 SECONDS;
-- Schritt 3: Versionsspeicher in TempDB überwachen
-- version_store_reserved_page_count zeigt wie viel Platz der Versionsspeicher belegt
SELECT
reserved_page_count,
reserved_space_kb / 1024.0 AS reserved_space_mb,
version_store_reserved_page_count,
version_store_reserved_page_count * 8.0 / 1024 AS version_store_mb
FROM sys.dm_db_file_space_usage; -- in TempDB ausführen!
|
Tipp: RCSI: Sofortiger Gewinn ohne Anwendungsänderung |
|---|
|
RCSI ist eine der wirkungsvollsten Maßnahmen mit dem besten Aufwand-Nutzen-Verhältnis. Keine Codeänderung, kein Neustart, ein ALTER DATABASE-Befehl — und Read-Write-Blocking ist für 80–90% der typischen OLTP-Workloads Geschichte. Der TempDB-Overhead ist bei korrekt dimensioniertem Versionsspeicher (Kapitel 13) sehr gut beherrschbar. |
6.6 Compatibility Level: Alter Code, neues Problem
Der Compatibility Level einer Datenbank steuert, welche SQL-Server-Version das Verhalten der Query-Engine imitiert. Das klingt harmlos und ist für Rollbacks und Migrationen auch wichtig — aber der Performance-Aspekt wird regelmäßig übersehen: der Compatibility Level beeinflusst den Cardinality Estimator.
SQL Server 2014 hat den Cardinality Estimator grundlegend überarbeitet (CE Version 120). Der neue Estimator schätzt Zeilenanzahlen moderner und genauer — in den meisten Fällen. Läuft deine Datenbank auf Compatibility Level 110 (SQL Server 2012), nutzt sie noch den alten CE, egal auf welcher SQL-Server-Version die Instanz läuft. Du zahlst Lizenz für SQL Server 2019, aber der Query Optimizer denkt wie im Jahr 2012. Kapitel 16 erklärt den Cardinality Estimator ausführlich und zeigt, wo CE 70 vs. CE 120+ konkrete Plan-Unterschiede erzeugt.
|
Praxisbeispiel: Sparfuchs & Partner: Kompatibilitätslevel 110 auf SQL 2019 |
|---|
|
Genau das war der Fall bei Sparfuchs (Kapitel 33): SQL Server 2019 als Instanz, aber alle Datenbanken auf Level 110. Das bedeutete CE 2012 — und damit schlechtere Schätzungen, suboptimale Pläne und verpasste Optimierungen aus sieben Jahren SQL-Server-Entwicklung. Allein der Wechsel auf Level 150 brachte bei mehreren Abfragen 30–60% Laufzeitverbesserung. |
|
Compatibility Level |
SQL Server Version |
CE Version |
Wichtige Features |
|---|---|---|---|
|
100 |
SQL Server 2008 |
CE 70 |
Kein In-Memory, kein Columnstore |
|
110 |
SQL Server 2012 |
CE 70 |
Columnstore (read-only) |
|
120 |
SQL Server 2014 |
CE 120 |
Neuer Cardinality Estimator |
|
130 |
SQL Server 2016 |
CE 130 |
Adaptive Join (ab Level 140) |
|
140 |
SQL Server 2017 |
CE 140 |
Adaptive Joins, Interleaved Execution |
|
150 |
SQL Server 2019 |
CE 150 |
Intelligent Query Processing (IQP) |
|
160 |
SQL Server 2022 |
CE 160 |
Parameter Sensitive Plan Optimization |
Tab. 6.3: Compatibility Level und ihre Auswirkungen
Das sichere Upgrade-Vorgehen: erst den Query Store aktivieren, dann den Compatibility Level erhöhen, dann überwachen. Wenn ein Plan schlechter wird, kannst du ihn im Query Store erzwingen (Forced Plan) — und in Ruhe analysieren warum. Mehr dazu in Kapitel 19 zum Query Store.
-- Aktuelle Compatibility Level aller Datenbanken
-- Alles unter 130 auf einem SQL 2019+ Server ist ein Upgrade-Kandidat
SELECT name, compatibility_level, create_date
FROM sys.databases
ORDER BY compatibility_level, name;
-- Compatibility Level erhöhen (erst Query Store aktivieren!)
-- Schritt 1: Query Store als Fallback bereitstellen
ALTER DATABASE MeineDatenbank SET QUERY_STORE = ON;
-- Schritt 2: Level erhöhen
ALTER DATABASE MeineDatenbank SET COMPATIBILITY_LEVEL = 150;
6.7 Database Scoped Configurations: Moderne Feineinstellung
Ab SQL Server 2016 gibt es eine elegante Zwischenlösung für einen häufigen Konflikt: Du willst den Compatibility Level erhöhen, aber eine bestimmte Datenbank verhält sich dann anders. Bisher war das ein Problem — Level hoch bedeutete für alle Datenbanken den neuen CE. Mit ALTER DATABASE SCOPED CONFIGURATION kannst du jetzt auf Datenbankebene einzelne Verhaltensweisen überschreiben, ohne den Compatibility Level anzutasten.
Das ist besonders wertvoll in drei Szenarien: Migration (eine Datenbank testweise auf neues Verhalten umstellen), Troubleshooting (Problem isolieren) und dauerhafter Betrieb (eine Datenbank braucht andere Parameter als die anderen).
MAXDOP auf Datenbankebene überschreiben
Den Server-MAXDOP haben wir in Kapitel 5 konfiguriert. DATABASE SCOPED CONFIGURATION erlaubt seit SQL Server 2016 (für Azure SQL bereits früher) eine Überschreibung auf Datenbankebene. Das ist nützlich, wenn du eine Reporting-Datenbank mit hohem MAXDOP und eine OLTP-Datenbank mit niedrigem MAXDOP auf derselben Instanz betreibst — ohne den Server-MAXDOP als Kompromiss setzen zu müssen.
LEGACY_CARDINALITY_ESTIMATION
Wenn du den Compatibility Level erhöhst und eine Datenbank mit dem neuen CE schlechtere Pläne bekommt, kannst du diese Datenbank auf den alten CE zurücksetzen — ohne den Compatibility Level zu senken und ohne die anderen Datenbanken zu betreffen. Das ist die elegantere Alternative zum Trace Flag 9481 (das global wirkt). Die Ursache der schlechteren Pläne solltest du danach trotzdem analysieren — Kapitel 16 erklärt die typischen Fallen des neuen CE.
PARAMETER_SNIFFING deaktivieren
Parameter Sniffing ist in der Regel hilfreich: SQL Server compiliert einen Plan anhand der ersten Parameterwerte und cached ihn. Das Problem entsteht, wenn die ersten Werte nicht repräsentativ sind — ein häufiges Problem das Kapitel 18 ausführlich behandelt. Als temporäre Maßnahme oder für spezifische Datenbanken mit stark variierenden Parameterverteilungen lässt sich Parameter Sniffing auf Datenbankebene deaktivieren. Das erzwingt für jede Ausführung einen frischen Compile — korrektere Pläne, aber höhere CPU-Last durch Compilation.
-- Alle aktuellen Scoped Configurations einer Datenbank anzeigen
SELECT name, value, value_for_secondary, is_value_default
FROM sys.database_scoped_configurations
ORDER BY name;
-- MAXDOP für diese Datenbank auf 4 begrenzen (Server-Wert wird überschrieben)
-- Sinnvoll: OLTP-Datenbank auf Instanz mit hohem Server-MAXDOP für Reporting
ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 4;
-- Auf Legacy Cardinality Estimator zurückwechseln (CE 70)
-- Nur als temporäre Maßnahme — Ursache der schlechteren Pläne analysieren!
ALTER DATABASE SCOPED CONFIGURATION SET LEGACY_CARDINALITY_ESTIMATION = ON;
-- Parameter Sniffing deaktivieren für diese Datenbank
-- Erzeugt bei jedem Aufruf einen neuen Plan — höhere CPU, aber konsistentere Performance
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF;
-- Einstellung zurücksetzen auf Server-Standard (NULL = Server-Wert gilt)
ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 0;
ALTER DATABASE SCOPED CONFIGURATION SET LEGACY_CARDINALITY_ESTIMATION = OFF;
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = ON;
|
Hinweis: Scoped Configuration: Für Secondary Replicas (AG) |
|---|
|
In Availability Groups kannst du mit dem Zusatz FOR SECONDARY einen anderen Wert für das Secondary-Replikat setzen. Das ist nützlich, wenn Reporting-Abfragen auf dem Secondary mit anderen MAXDOP-Werten oder einem anderen CE besser funktionieren als auf dem Primary — ohne die Konfiguration des Primary zu berühren. |
6.8 Virtual Log Files: Wenn das Transaktionslog aus dem Ruder läuft
|
Definition: Virtual Log File (VLF) |
|---|
|
SQL Server unterteilt das Transaktionslog intern in Virtual Log Files (VLFs). Jede physische Log-Datei (.ldf) besteht aus mehreren VLFs. VLFs sind die kleinste Einheit für Log-Truncation: ein VLF kann erst freigegeben werden, wenn alle Transaktionen darin abgeschlossen sind und ein Log-Backup gemacht wurde. Wie das Write-Ahead Logging intern funktioniert, erklärt Kapitel 4. |
Solange VLFs eine vernünftige Größe haben und nicht zu viele werden, ist das Konzept unsichtbar und harmlos. Das Problem entsteht durch schlechte Konfiguration: wenn das Transaktionslog hunderte oder tausende Male um jeweils 1 MB wächst (der katastrophale Autogrowth-Standardwert vergangener SQL-Server-Versionen), entstehen tausende von VLFs. SQL Server 2022 hat das Standardverhalten verbessert, aber ältere Datenbanken schleppen das Problem mit.
Die Konsequenzen zu vieler VLFs:

Abb. 6.2: VLF-Wachstum durch häufige Autogrowth-Ereignisse
Das Sparfuchs-Extrembeispiel aus Kapitel 33 verdeutlicht das eindrücklich: 48.312 VLFs in der Produktionsdatenbank — entstanden durch 847 Autogrowth-Ereignisse à 1 MB innerhalb von nur 120 Minuten. Die Recovery-Zeit nach einem Neustart: über 40 Minuten. Eine korrekt konfigurierte Datenbank gleicher Größe würde in unter 2 Minuten starten.
-- VLFs pro Datenbank zählen (SQL Server 2012+)
-- Über 1.000 VLFs sind ein klares Warnsignal, über 5.000 ein ernstes Problem
SELECT DB_NAME(database_id) AS Datenbank,
COUNT(*) AS VLF_Anzahl
FROM sys.dm_db_log_info(NULL) -- NULL = aktuelle DB, 0 = alle DBs
GROUP BY database_id
ORDER BY VLF_Anzahl DESC;
-- Detailansicht für eine bestimmte Datenbank
-- vlf_status: 0 = inaktiv (wiederverwendbar), 2 = aktiv (enthält Daten)
SELECT vlf_begin_offset, vlf_size_mb, vlf_sequence_number, vlf_status
FROM sys.dm_db_log_info(DB_ID('MeineDatenbank'))
ORDER BY vlf_begin_offset;
Zu viele VLFs beheben
Die Lösung ist simpel aber erfordert ein Wartungsfenster: das Transaktionslog verkleinern und dann auf die richtige Zielgröße wachsen lassen. Das ist einer der wenigen legitimen SHRINK-Fälle.
-- Schritt 1: Log-Backup machen (damit aktive VLFs freigegeben werden können)
BACKUP LOG MeineDatenbank TO DISK = 'NUL'; -- NUL = nur für Demo, echten Pfad verwenden!
-- Schritt 2: Log-Datei physisch verkleinern
-- Achtung: erst prüfen wie die Log-Datei heißt
SELECT name, physical_name FROM sys.database_files WHERE type = 1;
-- Log shrink (temporär erlaubt um VLFs zurückzusetzen)
DBCC SHRINKFILE (MeineDatenbank_log, 1); -- auf 1 MB verkleinern
-- Schritt 3: Log auf Zielgröße wachsen lassen — EINMALIG und mit sinnvoller Schrittweite
-- Faustformel: Zielgröße = typischer Transaktionslog-Verbrauch * 1,5 bis 2
-- Mit 8 GB Zielgröße entstehen nur wenige VLFs (SQL Server wählt dann große VLF-Segmente)
ALTER DATABASE MeineDatenbank
MODIFY FILE (NAME = MeineDatenbank_log, SIZE = 8192MB, FILEGROWTH = 2048MB);
|
Tipp: Autogrowth-Schrittweite ist entscheidend |
|---|
|
Sparfuchs hatte 847 Autogrowth-Ereignisse in 120 Minuten à 1 MB — das ergibt (mindestens) hunderte neue VLFs pro Stunde. Die Lösung: Autogrowth auf 256–1024 MB setzen. Dann entstehen pro Autogrowth-Ereignis wenige große VLFs statt viele kleine. Und der IO-Spike pro Ereignis ist seltener, auch, wenn er größer ist — ein seltener großer Spike ist besser als ständige kleine Unterbrechungen. |
6.9 Auto Update Statistics: Pflicht, aber mit Verstand
SQL Server braucht aktuelle Statistiken, um gute Ausführungspläne zu erstellen. Die Optionen AUTO_CREATE_STATISTICS und AUTO_UPDATE_STATISTICS sollten grundsätzlich eingeschaltet sein — das ist Standard und richtig so. Aber es gibt eine wichtige Nuance.
Das Standard-Update-Schwellwert: eine Statistik wird aktualisiert, wenn 20% der Zeilen geändert wurden (plus 500 bei kleinen Tabellen). Das klingt konservativ, ist es aber nicht. Bei einer Tabelle mit 10 Millionen Zeilen müssen also 2 Millionen Zeilen geändert werden, bevor die Statistik automatisch aktualisiert wird. Bis dahin kann SQL Server mit falschen Schätzungen arbeiten und schlechte Pläne wählen. Kapitel 16 erklärt die Auswirkungen veralteter Statistiken auf Ausführungspläne im Detail.
Ab SQL Server 2016 gibt es den Trace Flag 2371 nicht mehr als Pflicht — stattdessen wurde das dynamische Statistik-Update-Modell eingeführt, das bei großen Tabellen häufiger aktualisiert. Voraussetzung: Compatibility Level 130 oder höher. Ein weiterer Grund, den Level auf aktuellem Stand zu halten.
AUTO_UPDATE_STATISTICS_ASYNC ist eine Einstellung die in großen Datenbanken wichtig werden kann. Ohne ASYNC: wenn SQL Server merkt, dass eine Statistik veraltet ist, aktualisiert er sie synchron — die Abfrage wartet. Mit ASYNC: die Abfrage läuft sofort mit dem alten Plan weiter, die Statistik wird im Hintergrund aktualisiert. Beim nächsten Lauf hat die Abfrage dann frische Statistiken. Der Kompromiss: eine Ausführung mit möglicherweise suboptimalem Plan, aber keine unerwartete Wartezeit für den Nutzer.
-- Status aller Statistik-Optionen prüfen
SELECT name,
is_auto_create_stats_on, -- sollte 1 sein
is_auto_update_stats_on, -- sollte 1 sein
is_auto_update_stats_async_on -- je nach Workload sinnvoll
FROM sys.databases
ORDER BY name;
-- Asynchrone Statistik-Updates aktivieren (für große, aktive DBs sinnvoll)
-- Abwägung: leicht veraltete Pläne vs. keine Wartezeit auf Statistik-Update
ALTER DATABASE MeineDatenbank SET AUTO_UPDATE_STATISTICS_ASYNC ON;
-- Stale Statistics manuell finden (Schwellwert 20% deutlich überschritten)
SELECT
OBJECT_NAME(s.object_id) AS Tabelle,
s.name AS Statistik,
sp.last_updated,
sp.rows,
sp.rows_sampled,
sp.modification_counter
FROM sys.stats s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
WHERE sp.modification_counter > sp.rows * 0.2 -- mehr als 20% geändert
AND sp.rows > 1000 -- kleine Tabellen ignorieren
ORDER BY sp.modification_counter DESC;
6.10 Page Verify: CHECKSUM ist Pflicht
Die PAGE_VERIFY-Option bestimmt, wie SQL Server prüft ob Seiten beim Schreiben und Lesen korrekt geblieben sind. Es gibt drei Optionen: NONE, TORN_PAGE_DETECTION und CHECKSUM. Die richtige Wahl ist CHECKSUM — immer, ohne Ausnahme.
|
Option |
Erkennung |
Overhead |
Empfehlung |
|---|---|---|---|
|
NONE |
Keine Prüfung |
Kein |
Niemals verwenden |
|
TORN_PAGE_DETECTION |
Partial-Writes (begrenzt) |
Minimal |
Veraltet, nicht mehr empfohlen |
|
CHECKSUM |
Korruption jeder Art |
Sehr gering (<1%) |
Standard — immer einschalten |
Tab. 6.4: PAGE_VERIFY Optionen
Mit CHECKSUM berechnet SQL Server beim Schreiben einer Seite einen Prüfwert und speichert ihn im Seitenkopf. Beim nächsten Lesen wird der Prüfwert neu berechnet und verglichen. Bei Abweichung meldet SQL Server Fehler 824 — und der Administrator weiß: hier liegt Dateikorruption vor, bevor das Problem eskaliert. Ohne CHECKSUM kann Korruption unbemerkt bestehen und erst beim Restore auffallen — dann ist es zu spät.
TORN_PAGE_DETECTION erkennt nur unvollständige Schreibzugriffe (wenn eine Seite zur Hälfte geschrieben wurde und dann der Strom ausfiel). Hardware-Korruption, stille Bitfehler, Storage-Probleme — das alles erkennt TORN_PAGE_DETECTION nicht. Der Performance-Vorteil gegenüber CHECKSUM ist marginal und rechtfertigt den Sicherheitsverlust in keiner Situation.
-- PAGE_VERIFY Status prüfen
-- page_verify_option_desc sollte "CHECKSUM" sein, nicht "NONE" oder "TORN_PAGE_DETECTION"
SELECT name, page_verify_option_desc
FROM sys.databases
WHERE page_verify_option_desc != 'CHECKSUM';
-- Auf CHECKSUM setzen
ALTER DATABASE MeineDatenbank SET PAGE_VERIFY CHECKSUM;
-- Korruption aktiv suchen: DBCC CHECKDB sollte in jedem Wartungsplan sein
-- Empfehlung: wöchentlich auf Produktionsdatenbanken, täglich auf kritischen
DBCC CHECKDB ('MeineDatenbank') WITH NO_INFOMSGS, ALL_ERRORMSGS;
6.11 Die model-Datenbank: Der vergessene Multiplikator
Hier ist eine Frage, die in vielen Betrieben niemand beantworten kann: Was ist in deiner model-Datenbank eingestellt? Die Antwort ist wichtig, denn model ist die Vorlage für jede neue Datenbank, die auf dieser Instanz erstellt wird. Jede CREATE DATABASE-Anweisung, jede Applikation die beim ersten Start eine neue Datenbank anlegt, jede Wiederherstellung — sie alle erben die Einstellungen von model.
Wenn model noch die Express-Defaults hat (Auto-Close ON), erbt jede neue Datenbank das Problem. Wenn model Recovery Model FULL hat aber kein Log-Backup-Job existiert, wächst der Log jeder neuen Datenbank ungebremst. Wenn model PAGE_VERIFY TORN_PAGE_DETECTION hat, hat die nächste Datenbank keinen echten Korruptionsschutz. Wer model richtig konfiguriert, hat weniger Arbeit für alle Zeit.
Was von model geerbt wird
Folgende Einstellungen werden von model auf neue Datenbanken übertragen:
Was NICHT von model geerbt wird
Nicht alles kommt von model. Die Datenbankgröße ist durch MAXSIZE und SIZE der CREATE DATABASE Anweisung bestimmbar. Backup-Jobs und Wartungspläne gelten pro Datenbank und müssen separat konfiguriert werden. Berechtigungen und Login-Mappings werden nicht übertragen. Der Name der neuen Datenbank ersetzt natürlich den model-Namen.
model korrekt konfigurieren
Die folgende Checkliste für model ist kurz aber wirkungsvoll. Einmal erledigt, profitieren alle zukünftigen Datenbanken automatisch. Beim Musterwerk-Audit (Kapitel 32) war das Anpassen von model der erste Schritt — und hat in einer Stunde die Grundlage für alle 12 Produktionsdatenbanken verbessert.
-- model-Datenbank prüfen: alle kritischen Einstellungen auf einen Blick
SELECT
name,
recovery_model_desc,
compatibility_level,
is_auto_close_on,
is_auto_shrink_on,
page_verify_option_desc,
is_auto_create_stats_on,
is_auto_update_stats_on,
is_read_committed_snapshot_on
FROM sys.databases
WHERE name = 'model';
-- model korrekt konfigurieren
-- Danach gilt: jede neue Datenbank startet mit diesen Einstellungen
USE master;
-- Anti-Pattern abschalten
ALTER DATABASE model SET AUTO_CLOSE OFF;
ALTER DATABASE model SET AUTO_SHRINK OFF;
-- Korruptionsschutz aktivieren
ALTER DATABASE model SET PAGE_VERIFY CHECKSUM;
-- Statistiken sauber konfigurieren
ALTER DATABASE model SET AUTO_CREATE_STATISTICS ON;
ALTER DATABASE model SET AUTO_UPDATE_STATISTICS ON;
-- Recovery Model für Produktion: FULL (Log-Backup-Job nicht vergessen!)
-- Für Dev/Test-Instanzen: SIMPLE ist sinnvoller
ALTER DATABASE model SET RECOVERY FULL;
-- Vernünftige Dateigrößen als Startpunkt (kein 1-MB-Wachstum mehr)
-- Anpassen je nach typischer Datenbankgröße in der Umgebung
ALTER DATABASE model MODIFY FILE
(NAME = modeldev, SIZE = 64MB, FILEGROWTH = 256MB);
ALTER DATABASE model MODIFY FILE
(NAME = modellog, SIZE = 64MB, FILEGROWTH = 256MB);
|
Tipp: Wer model richtig konfiguriert, hat weniger Arbeit für alle Zeit |
|---|
|
Einmal gut eingestellt, ist model der Assistent der nie schläft: jede neue Datenbank startet mit korrekten Einstellungen, vernünftigen Dateigrößen und aktiviertem Korruptionsschutz. Der Aufwand: 5 Minuten. Der Nutzen: jede zukünftige Datenbank auf dieser Instanz. Prüfe model nach jeder SQL-Server-Installation und nach jedem Major-Upgrade — Microsoft setzt bei Upgrades nicht immer alle model-Einstellungen auf die neuen Empfehlungen. |
6.12 Diagnose: Datenbankeinstellungen auf einen Blick
Symptome
|
Hinweis: Woran merkst du, dass hier ein Problem vorliegt? |
|---|
|
• Nutzer berichten über sporadisch träge erste Verbindungen nach Ruhephasen → Auto-Close. |
|
• sys.dm_db_index_physical_stats zeigt dauerhaft hohe Fragmentierung, obwohl regelmäßig Rebuilds laufen → Auto-Shrink untergräbt die Wartung. |
|
• Das Transaktionslog wächst unbegrenzt → FULL-Modell ohne Log-Backup. |
|
• Datenbankstart nach Crash dauert unverhältnismäßig lang → zu viele VLFs. |
|
• CHECKDB meldet 824-Fehler → PAGE_VERIFY war nicht CHECKSUM und hat Korruption verschleiert. |
|
• Abfrageperformance ist schlechter als erwartet für die Hardware → Compatibility Level zu niedrig. |
|
• Lese-Schreib-Blocking trotz kurzer Transaktionen → RCSI nicht aktiviert. |
|
• Alle neuen Datenbanken haben dieselben Fehlkonfigurationen → model nicht angepasst. |
So misst du das
|
Tipp: Datenbankoptionen mit einer Query prüfen |
|---|
|
Die folgende Abfrage gibt für alle Datenbanken die kritischen Einstellungen aus. Alles was von den Sollwerten abweicht, ist direkter Handlungsbedarf. Diese Query gehört in jeden Konfigurationsaudit. |
-- Übersicht kritischer Datenbankeinstellungen — eine Query, alle Probleme
SELECT
name AS Datenbank,
recovery_model_desc AS RecoveryModel,
compatibility_level AS CompLevel,
is_auto_close_on AS AutoClose, -- 0 = gut, 1 = Problem
is_auto_shrink_on AS AutoShrink, -- 0 = gut, 1 = Problem
page_verify_option_desc AS PageVerify, -- sollte CHECKSUM sein
is_auto_create_stats_on AS AutoCreateStats, -- 1 = gut
is_auto_update_stats_on AS AutoUpdateStats, -- 1 = gut
is_read_committed_snapshot_on AS RCSI, -- 1 = Blocking reduziert
log_reuse_wait_desc AS LogWaitReason, -- warum Log nicht truncaten?
CASE WHEN recovery_model_desc = 'FULL'
AND log_reuse_wait_desc = 'LOG_BACKUP'
THEN 'WARNUNG: FULL ohne Log-Backup!'
ELSE 'OK'
END AS LogStatus
FROM sys.databases
WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')
ORDER BY name;
Typische Fehlinterpretationen
|
Warnung: Was täuscht und was wirklich gefährlich ist |
|---|
|
• "Auto-Close spart Ressourcen" — das Gegenteil ist wahr. Der ständige Open/Close-Zyklus kostet mehr als eine dauerhaft geöffnete Datenbank. |
|
• "SIMPLE Recovery ist unsicher" — falsch. Für Dev/Test und DWH ist SIMPLE vollkommen korrekt. Unsicher ist FULL ohne Log-Backup. |
|
• "Viele VLFs sind harmlos" — bis zur ersten Recovery-Situation. 48.000 VLFs bedeuten stundenlange Recovery-Zeit nach einem ungeplanten Neustart. |
|
• "Compatibility Level muss immer auf dem neuesten Stand sein" — bei einer Migration zuerst auf dem alten Level bleiben, testen, dann erhöhen. Nie blind updaten. |
|
• "RCSI verursacht TempDB-Probleme" — nur bei falsch dimensionierter TempDB oder sehr langen Transaktionen. Bei korrekt konfigurierter TempDB ist RCSI problemlos. |
|
• "Database Scoped Configuration überschreibt den Compatibility Level" — falsch. Sie überschreibt einzelne Verhaltensweisen wie CE oder Parameter Sniffing, nicht den Level selbst. |
Erste Gegenmaßnahmen
|
Tipp: Sofort-Checkliste nach Datenbankinstallation |
|---|
|
1. ALTER DATABASE model SET AUTO_CLOSE OFF — Basis für alle zukünftigen Datenbanken legen. |
|
2. ALTER DATABASE model SET AUTO_SHRINK OFF — bedingungslos. |
|
3. model PAGE_VERIFY auf CHECKSUM setzen — und alle vorhandenen Datenbanken prüfen. |
|
4. Recovery Model klären: für Produktion FULL + Log-Backup-Job einrichten (Sofort!), für Dev/Test SIMPLE. |
|
5. VLFs zählen: über 1.000 → Wartungsfenster einplanen und Log neu aufbauen. |
|
6. Compatibility Level: auf SQL Server 2019 mindestens Level 130, besser 150 — mit Query Store als Fallback. |
|
7. RCSI prüfen und aktivieren: in OLTP-Umgebungen mit Blocking-Problemen sofortiger Gewinn. |
|
8. Database Scoped Configurations als präzises Werkzeug nutzen, nicht als Dauerlösung. |
Zusammenfassung
Datenbankeinstellungen sind keine graue Theorie — sie entscheiden täglich darüber, ob eine Datenbank stabil und performant läuft oder still vor sich hin leidet. Die wichtigsten Erkenntnisse:
Eine einzelne Query auf sys.databases reicht aus, um den Status aller Datenbanken auf einen Blick zu bewerten. Es gibt keinen guten Grund, das nicht regelmäßig zu überprüfen — am besten direkt nach der Installation und danach monatlich im Zuge der Basis-Checks aus Kapitel 31.
Was du jetzt tun solltest: öffne SSMS, öffne eine neue Query-Session gegen deine Instanz, und führe die Übersichtsquery aus Abschnitt 6.12 aus. Dann schau dir an, was sys.databases über deine Produktionsdatenbanken sagt. Sei ehrlich mit dir selbst.
Ausblick auf Kapitel 7: Wir verlassen die vertraute Windows-Welt und schauen uns SQL Server on Linux an. Seit Version 2017 läuft SQL Server auf Red Hat, Ubuntu und SLES — und das erstaunlich gut. Kapitel 7 erklärt die SQLPAL-Schicht, die das möglich macht, zeigt die Feature-Unterschiede zu Windows und geht auf Performance-Besonderheiten ein, die man unter Linux kennen muss.
Kapitel 7
