Collation & Typ-Rangfolge · 10 Mio. Zeilen
Implizite Konvertierung: 3 Reads gegen 27.469 Reads
Wenn .NET/EF einen Unicode-Parameter (@p nvarchar) an eine varchar-Spalte übergibt, zwingt die Typ-Rangfolge unter Standard-SQL-Collation zu einem Zeilen-Scan:
nvarchar (Scan)
27.469 Reads · 446 ms
Passend (Seek)
3 Reads · 2,7 ms
Unter SQL-Sortierung muss SQL Server jede einzelne Zeile umwandeln (Plan-Warnung: CONVERT_IMPLICIT, 3.011 ms CPU-Zeit). Aus 3 Seitengriffen wird ein massiver Scan. Labor-Messdaten & Sortierungs-Matrix ansehen →
Mengenübergabe & Cache · 10 Mio. Zeilen
Lange IN-Listen (2.000 IDs) und Ad-hoc-Flut
Wie übergibt man hunderte oder tausende IDs aus C# an SQL Server? Vier Verfahren bei 10 bis 2.000 IDs und bei 50.000 Einzelaufrufen gegeneinander gemessen:
Literal-Liste
6.376 KB Cache · 589 ms
TVP (2.000 IDs)
200 KB Cache · 222 ms
Einzelparameter blähen den Plan-Cache auf; 50.000 Ad-hoc-Abfragen belegen 781 MB Speicher gegen 0,1 MB bei parametrisierten Aufrufen. Table-Valued Parameters (TVP) liefern einen stabilen Plan ohne Kompilierlast. Labor-Messdaten & 4-Methoden-Vergleich ansehen →
Datenmodell & Speicher · 1 Mio. Zeilen
Zusammengesetzter Textschlüssel vs. Surrogate Key
Ein dreiteiliger Fachschlüssel (Mandant + Bereich + Artikelnummer, ca. 56 Bytes) wiederholt sich in jeder Kindtabelle und jedem Index:
Textschlüssel
287 MB · 19.075 Reads
Surrogate int
65 MB · 3.919 Reads
Der Textschlüssel macht Tabellen und Indizes 4,4-mal so groß und den Verbund über alle Kinder 2,5-mal so langsam (1.142 ms vs. 471 ms). Der Fachschlüssel bleibt als UNIQUE-Constraint auf der Elterntabelle erhalten. Labor-Messdaten & Metadaten-Skript ansehen →
SARGability · 10 Mio. Zeilen
Führender Platzhalter (LIKE '%berg')
Eine Endungssuche wie WHERE Firma LIKE '%berg' ist nicht sargable – selbst mit Index auf Firma liest SQL Server alle 49.001 Indexseiten:
Standard LIKE
49.022 Reads · 3.890 ms
REVERSE + Idx
43 Reads · 6,3 ms (×617)
Mit einer berechneten Spalte FirmaRev AS REVERSE(Firma) und Index wird aus der Endungssuche ein gezielter Präfix-Seek. Das senkt die Lesevorgänge um den Faktor ×1.140 und die Laufzeit von 4 Sekunden auf 6,3 Millisekunden. Labor-Messdaten & Berechnete Spalte ansehen →
Servergrenzen · 1 Mio. Zeilen
Linked Server vs. OPENQUERY & Collation-Falle
Abfragen über Instanz- und Datenbankgrenzen kippen je nach Syntax und Sortierung in völlig unterschiedliche Ausführungspläne:
OPENQUERY
167.982 Reads · 1.224 ms
Linked 4-part
746 ms (stabiler Plan)
Bei großen Verbünden zwingt OPENQUERY in zeilenweise Nested Loops. Weicht zudem die Collation der Zieldatenbank ab, verhindert die implizite Konvertierung den Index Seek (Faktor ×119 I/O). Labor-Messdaten & Remote-Vergleich ansehen →
Normalisierung · 1 Mio. Zeilen
Klartext-Spalten vs. Nachschlagetabellen (3NF)
Stehen Attribute wie Stadt und Zahlungsart als Klartext in der Bewegungstabelle statt als schlanker int-Fremdschlüssel:
Klartext-Scan
4.074 Reads · 1.518 ms
3NF + Join
1.608 Reads · 33 ms (×46,4)
Auf der kleinen Nachschlagetabelle kostet die Mustersuche fast nichts, bevor über die kompakte StadtId gejoint wird – 46,4-mal schneller. Auch die Widerspruchsprüfung sinkt von 21.584 auf 32 Reads. Labor-Messdaten & Modellierungsvergleich ansehen →
Prozedur gegen View · 10 Mio. Kunden
Erst alles berechnen, dann filtern: Faktor ×2.600
Im gemessenen Fall (synthetische Daten, kalter Cache) dieselbe Frage – Umsatz der Kunden einer Stadt – einmal über eine View, einmal über eine Prozedur ohne Parameter, deren Ergebnis erst außen gefiltert wird:
Prozedur, außen gefiltert
134.172 ms
Die View lässt den Filter bis an den Index durch; die Prozedur rechnet alle 10 Mio. Kunden durch. Eine Mehranweisungs-Funktion liegt mit 53.095 ms dazwischen. Mit Parameter ist die Prozedur so schnell wie die View (54 ms). Labor-Messdaten ansehen →
Partitionierung · 100 Mio. Buchungen
Einen Monat entfernen: 14.082 ms gegen 8,5 ms
Ein Monat (833.333 Zeilen) wird aus 100 Mio. Buchungen entfernt – per DELETE oder durch Leeren der Monatspartition:
DELETE
14.082 ms mit Datumsindex (ohne: 67.298 ms)
Partition leeren
8,5 ms (×1.657)
DELETE protokolliert jede Zeile und pflegt jeden Index; das Leeren einer Partition gibt nur deren Seiten frei. Voraussetzung: Der Monat liegt genau in einer Partition, alle Indizes sind gleich partitioniert. Labor-Messdaten ansehen →
Partitionierung · 100 Mio. Buchungen
Partitionierung macht Abfragen nicht automatisch schneller
Dieselben Buchungen, einmal mit einfachem Index nach Datum, einmal nach Monat partitioniert:
Konto, partitioniert
285 ms
Konto, nicht partitioniert
45 ms
Die Kontoabfrage kennt den Monat nicht und sucht in allen 120 Partitionen. Auch die Monatssumme war partitioniert nicht schneller: 244 ms gegen 173 ms mit einfachem Datumsindex. Partitionierung dient der Datenpflege; für Abfragen ersetzt sie den passenden Index nicht. Labor-Messdaten ansehen →
Join · 100 Mio. Kunden, 400 Mio. Positionen
Join über drei Tabellen: Faktor ×632
Gesucht sind die 400 Positionen der Kunden einer Stadt – ohne und mit Indizes auf Stadt und Fremdschlüsseln:
Nur Primärschlüssel
6.843.764 Reads · 41.982 ms
Mit Indizes
3.144 Reads · 66 ms
Mit drei Unterabfragen je Kunde statt des Joins: 50.737 ms gegen 232 ms (×219). Labor-Messdaten ansehen →