SQL Server: Fehlende Indizes mit DMVs finden und bewerten

SQL Server: Fehlende Indizes mit DMVs finden und bewerten

Die Bestellungssuche braucht plötzlich mehrere Sekunden. CPU und Datenträger arbeiten, obwohl am Ende nur zwölf Zeilen erscheinen. Fehlt ein Index? SQL Server führt darüber tatsächlich eine Art Wunschzettel: Beim Optimieren von Abfragen merkt sich das System, welche zusätzlichen Indizes möglicherweise geholfen hätten.

Dieser Wunschzettel ist wertvoll – aber kein fertiger Einkaufsplan. Wer jeden Vorschlag ungeprüft umsetzt, beschleunigt vielleicht eine Suche und bremst dafür sämtliche Bestellimporte. Entscheidend ist deshalb nicht nur, fehlende Indizes zu finden, sondern ihre Wirkung richtig einzuschätzen.

Was die Missing-Index-DMVs zeigen – und was nicht

Dynamic Management Views, kurz DMVs, liefern interne Zustands- und Leistungsinformationen von SQL Server. Die Views mit dem Präfix sys.dm_db_missing_index_ zeigen Indexvorschläge, die während der Abfrageoptimierung entstehen.

Der Optimizer – die Komponente, die den Ausführungsweg einer Abfrage plant – schätzt dabei: Mit einem passenden zusätzlichen Index könnte dieser Zugriff günstiger werden. Er baut den Index jedoch nicht probeweise und misst auch keine tatsächliche Beschleunigung.

Für die Auswertung sind drei Views zentral:

  • sys.dm_db_missing_index_details: betroffene Tabelle und vorgeschlagene Spalten.
  • sys.dm_db_missing_index_groups: verbindet einen Indexvorschlag mit seiner Gruppe.
  • sys.dm_db_missing_index_group_stats: Nutzungszähler und geschätzte Kostenvorteile dieser Gruppe.

Die Vorschläge betreffen nicht gruppierte Rowstore-Indizes, also klassische zusätzliche Indexstrukturen neben der eigentlichen Datenspeicherung. Sie ersetzen keine vollständige Indexanalyse: Gefilterte oder eindeutige Indizes werden beispielsweise nicht vorgeschlagen. Auch die optimale Reihenfolge der Schlüsselspalten liefert der Wunschzettel nicht.

Wichtig: Ein leerer Wunschzettel beweist nicht, dass die Indizierung optimal ist. Nicht jede Optimierung erzeugt einen Eintrag, etwa bei sogenannten trivialen Plänen, für die SQL Server keine umfassende kostenbasierte Optimierung benötigt.

Die drei DMVs verbinden

Führen Sie diese Abfrage im Kontext der untersuchten Datenbank aus:

SELECT
    d.index_handle,
    g.index_group_handle,
    d.[statement] AS target_table,
    d.equality_columns,
    d.inequality_columns,
    d.included_columns,
    s.unique_compiles,
    s.user_seeks,
    s.user_scans,
    s.last_user_seek,
    s.last_user_scan,
    s.avg_total_user_cost,
    s.avg_user_impact,
    CAST(
        (CONVERT(float, s.user_seeks) + s.user_scans)
        * s.avg_total_user_cost
        * (s.avg_user_impact / 100.0)
        AS decimal(28, 2)
    ) AS estimated_benefit_score
FROM sys.dm_db_missing_index_details AS d
INNER JOIN sys.dm_db_missing_index_groups AS g
    ON g.index_handle = d.index_handle
INNER JOIN sys.dm_db_missing_index_group_stats AS s
    ON s.group_handle = g.index_group_handle
WHERE d.database_id = DB_ID()
ORDER BY estimated_benefit_score DESC;

Der Filter auf DB_ID() begrenzt die Ausgabe auf die aktuelle Datenbank. Ohne ihn können auf einer SQL-Server-Instanz Vorschläge aus mehreren Datenbanken erscheinen.

Bewusst erzeugt die Abfrage keine fertigen CREATE INDEX-Befehle. Ein ausführbares Skript wirkt schnell wie eine Handlungsempfehlung. Tatsächlich beginnt nach dieser Liste erst die fachliche Bewertung.

Equality-, Inequality- und INCLUDE-Spalten verstehen

Stellen Sie sich einen Index wie ein mehrstufig sortiertes Telefonbuch vor. Entscheidend ist, nach welchen Merkmalen es sortiert ist – und welche Informationen lediglich zusätzlich darin stehen.

Equality-Spalten: gezielt eingrenzen

equality_columns enthält Spalten mit Gleichheitsbedingungen, beispielsweise CustomerID = 42 oder Status = 'Open'.

Diese Spalten gehören bei einem typischen zusammengesetzten Index vor die Bereichsspalten. Ihre Reihenfolge untereinander verlangt jedoch weitere Prüfung: Welche Abfragen sollen den Index ebenfalls nutzen? Welche Spaltenkombination grenzt früh sinnvoll ein?

„Die selektivste Spalte immer zuerst“ ist dafür zu pauschal. Ein Index auf (CustomerID, Status) unterstützt andere Zugriffsmuster als einer auf (Status, CustomerID), selbst wenn beide die konkrete Abfrage ähnlich gut bedienen.

Inequality-Spalten: Bereiche durchsuchen

inequality_columns nennt Spalten mit Bereichsbedingungen, etwa OrderDate >= ... oder Amount > ....

Nach einem Bereich auf einer führenden Schlüsselspalte lassen sich nachfolgende Schlüsselspalten häufig nicht mehr gleichermaßen zur Eingrenzung des zusammenhängenden Suchbereichs nutzen. Sie können dennoch für weitere Filterung oder Abdeckung hilfreich sein.

Gerade bei reinen Ungleichheitsbedingungen sind die Kostenschätzungen der Missing-Index-Funktion weniger zuverlässig.

INCLUDE-Spalten: den Rückweg sparen

included_columns enthält zusätzliche Nutzdaten. Diese stehen auf der Blattebene des Index, bestimmen aber nicht seine Sortierung.

Angenommen, eine Abfrage filtert nach Kunde, Status und Bestelldatum und liefert zusätzlich den Gesamtbetrag. Dann wäre ein Index mit Schlüsseln (CustomerID, Status, OrderDate) und INCLUDE (TotalAmount) ein plausibler Kandidat.

Er kann die Abfrage abdecken: Alle benötigten Spalten liegen im Index. Zusätzliche Zugriffe auf die Basisdaten, sogenannte Key Lookups, können entfallen. Doch breite INCLUDE-Listen vergrößern den Index – auch dann, wenn die Empfehlung keinerlei Warnung dazu ausgibt.

Vorschläge nach Nutzen priorisieren

Der berechnete estimated_benefit_score verbindet drei Größen:

  • user_seeks und user_scans: gezählte Benutzerzugriffe, bei denen der vorgeschlagene Index hätte genutzt werden können.
  • avg_total_user_cost: durchschnittliche geschätzte Kosten der betreffenden Abfragen ohne den vorgeschlagenen Index.
  • avg_user_impact: durchschnittlich geschätzte prozentuale Kostenreduktion durch den Index.

Die Multiplikation ist eine praktische Rangierhilfe, keine offizielle Messung eingesparter Laufzeit. Optimizer-Kosten sind interne Vergleichswerte, keine Millisekunden. Auch beziehen sich die Zähler nicht auf Zugriffe auf einen bereits existierenden Index.

Ein Seek ist außerdem nicht automatisch billig, ein Scan nicht automatisch schlecht. Ein großer Bereichszugriff kann teuer sein; ein Scan über eine winzige Tabelle dagegen völlig angemessen.

Prüfen Sie deshalb neben dem Score:

  • Ist der Vorschlag aktuell oder stammt die letzte Nutzung aus einem einmaligen Datenexport?
  • Betrifft er einen geschäftskritischen Prozess?
  • Entstehen die hohen Werte durch viele kleine oder wenige sehr teure Abfragen?
  • Würde ein schmalerer Index bereits ausreichend helfen?

unique_compiles zeigt ergänzend, wie viele Kompilierungen beziehungsweise Neukompilierungen von dem Vorschlag profitieren würden. Das ist kein Ausführungszähler.

Grenzen: Erfassungszeitraum, Rechte und 600 Einträge

Die Daten sind nicht dauerhaft gespeichert. Nach einem Neustart beginnt die Sammlung erneut. Auch Datenbankereignisse und Metadatenänderungen, insbesondere Indexänderungen an betroffenen Tabellen, können Einträge entfernen.

Den Startzeitpunkt der Instanz ermitteln Sie so:

SELECT sqlserver_start_time
FROM sys.dm_os_sys_info;

Dieser Zeitpunkt ist nur eine Obergrenze des möglichen Beobachtungszeitraums, kein garantiertes Startdatum jedes einzelnen Eintrags. Speichern Sie wichtige Auswertungen deshalb mit Zeitstempel und betrachten Sie repräsentative Geschäftszyklen: Tagesgeschäft, Nachtverarbeitung und Monatsabschluss können völlig unterschiedliche Indexbedürfnisse haben.

Die Missing-Index-Erfassung ist auf 600 Einträge beziehungsweise Gruppen begrenzt. Ist diese Grenze erreicht, kommen keine weiteren Vorschläge hinzu. Die Liste ist dann möglicherweise unvollständig.

Auf SQL Server benötigen diese Missing-Index-DMVs bis einschließlich SQL Server 2019 typischerweise VIEW SERVER STATE, ab SQL Server 2022 VIEW SERVER PERFORMANCE STATE. Für Azure SQL Database gelten abweichende Berechtigungsregeln. Vergeben Sie dafür nicht vorschnell administrative Vollrechte.

Vorhandene Indizes und Schreibkosten prüfen

Vor jedem neuen Index gehört der bestehende Bestand auf den Tisch. Diese Abfrage zeigt die Indexspalten der aktuell betroffenen Tabellen:

SELECT
    OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,
    OBJECT_NAME(i.object_id) AS table_name,
    i.name AS index_name,
    i.type_desc,
    i.is_unique,
    i.is_disabled,
    i.filter_definition,
    c.name AS column_name,
    ic.key_ordinal,
    ic.is_descending_key,
    ic.is_included_column
FROM sys.indexes AS i
INNER JOIN sys.index_columns AS ic
    ON ic.object_id = i.object_id
   AND ic.index_id = i.index_id
INNER JOIN sys.columns AS c
    ON c.object_id = ic.object_id
   AND c.column_id = ic.column_id
WHERE i.object_id IN
(
    SELECT d.object_id
    FROM sys.dm_db_missing_index_details AS d
    WHERE d.database_id = DB_ID()
)
ORDER BY
    schema_name,
    table_name,
    index_name,
    ic.is_included_column,
    ic.key_ordinal,
    ic.index_column_id;

Ein häufiger Projektfehler: Für ähnliche Vorschläge entstehen drei fast identische Indizes, obwohl eine durchdachte Erweiterung ausgereicht hätte. Umgekehrt ist ein einziger riesiger „Universalindex“ selten die beste Lösung.

Prüfen Sie Schlüsselreihenfolge, INCLUDE-Spalten und Filter gemeinsam. Bei nicht gruppierten Indizes ist außerdem der Clustered-Key als Zeilenverweis implizit vorhanden; diese impliziten Spalten erscheinen nicht vollständig in sys.index_columns.

Jeder zusätzliche Index braucht Speicher, Cache und Wartung. Inserts und Deletes müssen ihn mitpflegen; Updates können ebenfalls zusätzliche Arbeit verursachen. Ein schnellerer Lesezugriff kann somit langsamere Schreibprozesse und mehr Transaktionsprotokoll bedeuten.

Änderungen mit Messungen validieren

Messen Sie vor und nach der Änderung mit repräsentativen Parametern und vergleichbarer Last. Für eine Testsitzung aktivieren Sie:

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

Führen Sie anschließend die betreffende Abfrage aus und deaktivieren Sie die Ausgabe danach:

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

Vergleichen Sie insbesondere logical reads, CPU-Zeit und Laufzeit. Im tatsächlichen Ausführungsplan kontrollieren Sie Zugriffswege, Key Lookups, Sortierungen und Abweichungen zwischen geschätzten und tatsächlichen Zeilenzahlen.

Ein Index Seek allein ist noch kein Erfolg. Entscheidend ist die Gesamtwirkung. Leeren Sie dafür nicht auf dem Produktivsystem den Plan- oder Datencache.

Der Query Store, SQL Servers historische Sammlung von Abfragen, Plänen und Laufzeitstatistiken, hilft beim Vergleich über längere Zeiträume. Beobachten Sie auch andere Abfragen und Schreibprozesse: Ein neuer Index kann mehrere Pläne verändern. Legen Sie deshalb möglichst einen Kandidaten nach dem anderen an.

Fazit

Missing-Index-DMVs sind ein hervorragender Ausgangspunkt, aber kein Autopilot. Sie zeigen geschätzte Chancen aus einem begrenzten Beobachtungsfenster.

Nutzen Sie die Vorschläge als Hypothesen: priorisieren, mit bestehenden Indizes abgleichen, Schreibkosten berücksichtigen und anschließend messen. Der beste Index ist nicht der mit dem höchsten DMV-Score, sondern derjenige, der Ihre reale Arbeitslast nachweislich verbessert.

Stand: November 2026. Funktionen, Versionen und Preise können sich seitdem geändert haben.


Sie möchten tiefer einsteigen? In unseren Schulungen vermitteln wir SQL Server, Power BI und KI praxisnah – online oder bei Ihnen vor Ort. Fragen? Kontaktieren Sie uns oder buchen Sie ein kostenloses Info-Gespräch.