Remus hat hilfreich darauf hingewiesen, dass sich die maximale Länge der VARCHAR
Spalte auf die geschätzte Zeilengröße auswirkt und daher die von SQL Server bereitgestellten Speicherzuweisungen.
Ich habe versucht, etwas mehr Nachforschungen anzustellen, um den Teil seiner Antwort zu erweitern, der von dieser Kaskade der Dinge ausgeht. Ich habe keine vollständige oder präzise Erklärung, aber hier ist, was ich gefunden habe.
Repro-Skript
Ich habe ein vollständiges Skript erstellt , das einen gefälschten Datensatz generiert, für den die Indexerstellung auf meinem Computer ungefähr 10-mal so lange dauert wie für die VARCHAR(256)
Version. Die verwendeten Daten sind genau die gleiche, aber die erste Tabelle verwendet , die tatsächlichen Längen von max 18
, 75
, 9
, 15
, 123
, und 5
, während alle Spalten verwenden , um eine maximale Länge des 256
in der zweiten Tabelle.
Eingabe der Originaltabelle
Hier sehen wir, dass die ursprüngliche Abfrage in ungefähr 20 Sekunden abgeschlossen ist und die logischen Lesevorgänge der Tabellengröße von ~1.5GB
(195 KB Seiten, 8 KB pro Seite) entsprechen.
-- CPU time = 37674 ms, elapsed time = 19206 ms.
-- Table 'testVarchar'. Scan count 9, logical reads 194490, physical reads 0
CREATE CLUSTERED INDEX IX_testVarchar
ON dbo.testVarchar (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO
Eingabe der VARCHAR (256) -Tabelle
In der VARCHAR(256)
Tabelle sehen wir, dass die verstrichene Zeit dramatisch zugenommen hat.
Interessanterweise erhöhen sich weder die CPU-Zeit noch die logischen Lesevorgänge. Dies ist sinnvoll, da die Tabelle exakt dieselben Daten enthält, erklärt jedoch nicht, warum die verstrichene Zeit so viel langsamer ist.
-- CPU time = 33212 ms, elapsed time = 263134 ms.
-- Table 'testVarchar256'. Scan count 9, logical reads 194491
CREATE CLUSTERED INDEX IX_testVarchar256
ON dbo.testVarchar256 (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO
I / O und Wartestatistik: Original
Wenn wir ein bisschen mehr Details erfassen (mithilfe von p_perfMon, einer Prozedur, die ich geschrieben habe ), können wir feststellen, dass der Großteil der E / A- Vorgänge für die LOG
Datei ausgeführt wird. Wir sehen eine relativ bescheidene Menge an E / A in der eigentlichen ROWS
Datei (der Hauptdatendatei), und der primäre Wartetyp ist LATCH_EX
, was auf einen Konflikt auf der Speicherseite hinweist.
Wir können auch sehen, dass meine sich drehende Scheibe laut Paul Randal irgendwo zwischen "schlecht" und "schockierend schlecht" liegt :)
E / A und Wartestatistik: VARCHAR (256)
Für die VARCHAR(256)
Version sehen die E / A- und Wartestatistiken völlig anders aus! Hier sehen wir eine enorme Zunahme der E / A in der Datendatei ( ROWS
), und die Standzeiten lassen Paul Randal jetzt einfach "WOW!" Sagen.
Es ist nicht überraschend, dass der Wartetyp # 1 jetzt ist IO_COMPLETION
. Aber warum wird so viel E / A generiert?
Tatsächlicher Abfrageplan: VARCHAR (256)
Aus dem Abfrageplan können wir ersehen, dass der Sort
Operator in der VARCHAR(256)
Version der Abfrage einen rekursiven Fehler aufweist (5 Ebenen tief!) . (In der Originalversion gibt es überhaupt keine Verschüttung.)
Fortschritt der Live-Abfrage: VARCHAR (256)
Mit sys.dm_exec_query_profiles können Sie den Fortschritt von Live-Abfragen in SQL 2014+ anzeigen . In der Originalversion kann das gesamte Table Scan
und Sort
ohne Verschütten verarbeitet werden ( spill_page_count
bleibt 0
durchgehend erhalten).
In der VARCHAR(256)
Version können wir jedoch feststellen, dass sich schnell Seitenverluste für den Sort
Bediener ansammeln . Hier ist eine Momentaufnahme des Abfragefortschritts, kurz bevor die Abfrage abgeschlossen ist. Die Daten hier werden über alle Threads hinweg aggregiert.
Wenn ich in jeden Thread einzeln grabe, sehe ich, dass 2 Threads die Sortierung innerhalb von etwa 5 Sekunden abschließen (insgesamt 20 Sekunden, nach 15 Sekunden, die für den Tabellenscan aufgewendet wurden). Wenn alle Threads mit dieser Geschwindigkeit weitergearbeitet hätten, wäre die VARCHAR(256)
Indexerstellung ungefähr zur selben Zeit wie die ursprüngliche Tabelle abgeschlossen worden.
Die verbleibenden 6 Threads werden jedoch viel langsamer ausgeführt. Dies kann an der Art und Weise liegen, wie der Speicher zugewiesen wird, und an der Art und Weise, wie die Threads von E / A gehalten werden, während sie Daten verschütten. Ich weiß es nicht genau.
Was kannst du tun?
Es gibt eine Reihe von Dingen, die Sie in Betracht ziehen könnten:
- Arbeiten Sie mit dem Anbieter zusammen, um ein Rollback auf eine frühere Version durchzuführen. Wenn dies nicht möglich ist, teilen Sie dem Anbieter mit, dass Sie mit dieser Änderung nicht zufrieden sind, damit er sie in einer zukünftigen Version zurücksetzen kann.
- Berücksichtigen Sie beim Hinzufügen Ihres Indexes,
OPTION (MAXDOP X)
wo X
eine niedrigere Zahl als die aktuelle Einstellung auf Serverebene ist. Als ich OPTION (MAXDOP 2)
diesen spezifischen Datensatz auf meinem Computer verwendete, wurde die VARCHAR(256)
Version in 25 seconds
(im Vergleich zu 3-4 Minuten mit 8 Threads!) Abgeschlossen . Es ist möglich, dass das Verschüttungsverhalten durch eine höhere Parallelität verstärkt wird.
- Wenn zusätzliche Hardwareinvestitionen möglich sind, analysieren Sie die E / A (den wahrscheinlichen Engpass) auf Ihrem System und erwägen Sie die Verwendung einer SSD, um die durch Überläufe verursachte E / A-Latenz zu verringern.
Weitere Lektüre
Paul White hat einen netten Blog-Beitrag über die Interna von SQL Server-Arten , die von Interesse sein könnten. Es geht ein wenig um Verschütten, Thread-Versatz und Speicherzuweisung für parallele Sortierungen.