Wer SQL Server administriert, stolpert früher oder später über zwei Themen, die Performance direkt beeinflussen:
Praxisnah erklärt – inkl. Kombi-Maintenance Script
- Index-Fragmentierung
- veraltete Statistiken (Statistics)
Beides hängt nicht technisch zusammen, wirkt aber gemeinsam auf Ausführungspläne und I/O-Last. In diesem Artikel zeige ich den praktischen Zusammenhang – inklusive zweier Script-Varianten, die Fragmentierung prüfen, Indizes warten und Statistics gleich mit aktualisieren.
Locker erklärt, aber produktionsnah.
Warum Fragmentierung überhaupt relevant ist
Bei vielen Inserts, Updates und Deletes entstehen Lücken und Seiten-Splits in Indizes. Die Daten liegen dann nicht mehr logisch sortiert hintereinander, sondern verteilt.
Folgen können sein:
- mehr I/O bei Scans
- schlechtere Cache-Effizienz
- längere Laufzeiten bei Reports / Batch-Jobs
Faustregeln:
| Fragmentierung | Maßnahme |
|---|---|
| < 5 % | ignorieren |
| 5–30 % | REORGANIZE |
| > 30 % | REBUILD |
Warum Statistics genauso wichtig sind
Statistics beschreiben die Datenverteilung für den Query Optimizer. Sind sie alt oder “dirty”, entstehen:
- falsche Cardinality-Schätzungen
- schlechte Join-Strategien
- unnötige Scans / Sorts
Wichtig:
Index-Maintenance aktualisiert Stats nicht immer automatisch:
| Aktion | Stats danach |
|---|---|
| REBUILD | automatisch frisch (Fullscan) |
| REORGANIZE | unverändert |
Deshalb ist eine Kombi-Wartung sinnvoll.
Script-Variante 1
Kombi-Maintenance (Index + Stats)
Diese Version:
- prüft Fragmentierung
- entscheidet REBUILD / REORGANIZE
- aktualisiert danach die betroffenen Stats
USE [DEINE DB];
GO
SET NOCOUNT ON;
DECLARE
@RebuildThreshold float = 30.0,
@ReorgThreshold float = 10.0,
@MinPageCount int = 500;
DECLARE
@SchemaName sysname,
@TableName sysname,
@IndexName sysname,
@Frag float,
@PageCount int,
@sql nvarchar(max);
DECLARE IndexCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT
s.name,
o.name,
i.name,
ips.avg_fragmentation_in_percent,
ips.page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i
ON i.object_id = ips.object_id AND i.index_id = ips.index_id
JOIN sys.objects o
ON o.object_id = i.object_id
JOIN sys.schemas s
ON s.schema_id = o.schema_id
WHERE o.type = 'U'
AND ips.index_id > 0
AND ips.page_count >= @MinPageCount
AND i.is_disabled = 0
AND i.is_hypothetical = 0
AND ips.avg_fragmentation_in_percent >= @ReorgThreshold;
OPEN IndexCursor;
FETCH NEXT FROM IndexCursor
INTO @SchemaName, @TableName, @IndexName, @Frag, @PageCount;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql = NULL;
IF @Frag >= @RebuildThreshold
BEGIN
SET @sql = N'
ALTER INDEX ' + QUOTENAME(@IndexName) + N'
ON ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) + N'
REBUILD;
UPDATE STATISTICS ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) +
N' (' + QUOTENAME(@IndexName) + N') WITH FULLSCAN;';
END
ELSE IF @Frag >= @ReorgThreshold
BEGIN
SET @sql = N'
ALTER INDEX ' + QUOTENAME(@IndexName) + N'
ON ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) + N'
REORGANIZE;
UPDATE STATISTICS ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) +
N' (' + QUOTENAME(@IndexName) + N');';
END
IF @sql IS NOT NULL
EXEC sys.sp_executesql @sql;
FETCH NEXT FROM IndexCursor
INTO @SchemaName, @TableName, @IndexName, @Frag, @PageCount;
END
CLOSE IndexCursor;
DEALLOCATE IndexCursor;
Eigenschaften
- Vollautomatisch
- Wartet nur Indizes über Schwellwert
- Aktualisiert Stats gezielt
- Produktionsnah einsetzbar
Script-Variante 2
Laufzeitoptimiert (Top-N Wartung)
Wenn Datenbanken groß sind, kann Variante 1 lange laufen. Deshalb begrenzen viele Admins pro Wartungsfenster die Anzahl der Indizes.
Diese Version bearbeitet nur die „schlimmsten“ N:
USE [Deine DB];
GO
SET NOCOUNT ON;
DECLARE
@RebuildThreshold float = 30.0,
@ReorgThreshold float = 10.0,
@MinPageCount int = 500,
@TopN int = 50;
DECLARE
@SchemaName sysname,
@TableName sysname,
@IndexName sysname,
@Frag float,
@PageCount int,
@sql nvarchar(max);
DECLARE IndexCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT TOP (@TopN)
s.name,
o.name,
i.name,
ips.avg_fragmentation_in_percent,
ips.page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i
ON i.object_id = ips.object_id AND i.index_id = ips.index_id
JOIN sys.objects o
ON o.object_id = i.object_id
JOIN sys.schemas s
ON s.schema_id = o.schema_id
WHERE o.type = 'U'
AND ips.index_id > 0
AND ips.page_count >= @MinPageCount
AND ips.avg_fragmentation_in_percent >= @ReorgThreshold
ORDER BY ips.avg_fragmentation_in_percent DESC, ips.page_count DESC;
OPEN IndexCursor;
FETCH NEXT FROM IndexCursor
INTO @SchemaName, @TableName, @IndexName, @Frag, @PageCount;
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Frag >= @RebuildThreshold
SET @sql = N'ALTER INDEX ' + QUOTENAME(@IndexName) + N'
ON ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) + N'
REBUILD;';
ELSE
SET @sql = N'ALTER INDEX ' + QUOTENAME(@IndexName) + N'
ON ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) + N'
REORGANIZE;';
EXEC sys.sp_executesql @sql;
FETCH NEXT FROM IndexCursor
INTO @SchemaName, @TableName, @IndexName, @Frag, @PageCount;
END
CLOSE IndexCursor;
DEALLOCATE IndexCursor;
Vorteile
- planbare Wartungsdauer
- geeignet für Nachtjobs
- reduziert Log- und I/O-Spitzen
Praxis-Lessons Learned
Ein paar Dinge, die in der Realität gern übersehen werden:
1. PageCount-Filter ist entscheidend
Zu hoch → nichts passiert
Zu niedrig → Wartung dauert ewig
2. Kleine Indizes bringen kaum Performancegewinn
Aber kosten trotzdem I/O bei Wartung.
3. Fragmentierung ≠ Performanceproblem
Oft sind Stats oder fehlende Indizes relevanter.
4. REBUILD erzeugt viel Log
Bei knappen Logfiles oder Storage-Issues vorsichtig einsetzen.
Typischer Wartungsfahrplan
So fahren es viele produktiv:
| Frequenz | Aktion |
|---|---|
| täglich | Top-N Reorg + Stats |
| wöchentlich | große Rebuilds |
| monatlich | Fullscan Stats |
Fazit
Index-Fragmentierung und Statistics sollten nicht getrennt betrachtet werden. Erst die Kombination sorgt dafür, dass:
- physische Datenstruktur sauber ist
- der Optimizer korrekte Pläne erzeugt
Mit den gezeigten Scripts lässt sich eine solide, automatisierte Wartung aufbauen — von „Quick Win“ bis produktionsreif skalierbar.
Und wie immer gilt:
Schwellenwerte, PageCount-Filter und Laufzeitbegrenzung an die eigene Umgebung anpassen — nicht blind übernehmen.
Wenn du das selbst nachbauen willst, starte konservativ:
- Reorg ab 10 %
- Rebuild ab 30–40 %
- PageCount ≥ 500 oder 1000
Und dann messen, nicht raten.
Happy Index Gardening 🌱