Ich habe eine ziemlich große Tabelle mit einer der Spalten, die XML-Daten mit einer durchschnittlichen Größe des XML-Eintrags von ~ 15 Kilobyte sind. Alle anderen Spalten sind reguläre Ints, Bigints, GUIDs usw. Um einige konkrete Zahlen zu haben, nehmen wir an, die Tabelle hat eine Million Zeilen und ist ~ 15 GB groß.
Was mir aufgefallen ist, dass diese Tabelle nur sehr langsam Daten auswählt, wenn ich alle Spalten auswählen möchte. Wenn ich es tue
SELECT TOP 1000 * FROM TABLE
Es dauert ungefähr 20-25 Sekunden, um die Daten von der Festplatte zu lesen - obwohl ich dem Ergebnis keine Reihenfolge auferlege. Ich starte die Abfrage mit dem kalten Cache (dh nach DBCC DROPCLEANBUFFERS
). Hier sind die IO-Statistikergebnisse:
Scan-Anzahl 1, logische Lesevorgänge 364, physikalische Lesevorgänge 24, Vorauslesevorgänge 7191, logische Lobs-Lesevorgänge 7924, physikalische Lobs-Lesevorgänge 1690, Lobs-Vorauslesevorgänge 3968.
Es werden ca. 15 MB Daten erfasst. Der Ausführungsplan zeigt den erwarteten Clustered Index Scan.
Auf der Festplatte ist außer meinen Abfragen kein E / A-Vorgang aktiv. Ich habe auch überprüft, dass die Fragmentierung des Clustered-Index nahe bei 0% liegt. Dies ist ein SATA-Laufwerk für Endverbraucher. Ich würde jedoch immer noch glauben, dass SQL Server in der Lage ist, die Tabelle schneller als ~ 100-150 MB / min zu scannen.
Das Vorhandensein eines XML-Felds bewirkt, dass sich die meisten Tabellendaten auf LOB_DATA-Seiten befinden (tatsächlich sind ~ 90% der Tabellenseiten LOB_DATA).
Ich denke, meine Frage lautet: Stimmt es, dass LOB_DATA-Seiten langsame Scans verursachen können, nicht nur aufgrund ihrer Größe, sondern auch, weil SQL Server den Clustered-Index nicht effektiv scannen kann, wenn viele LOB_DATA-Seiten in der Tabelle enthalten sind?
Noch umfassender: Wird eine solche Tabellenstruktur / ein solches Datenmuster als sinnvoll angesehen? Empfehlungen für die Verwendung von Filestream enthalten normalerweise viel größere Felder, sodass ich diesen Weg nicht wirklich einschlagen möchte. Ich habe keine guten Informationen zu diesem bestimmten Szenario gefunden.
Ich habe über eine XML-Komprimierung nachgedacht, diese muss jedoch auf dem Client oder mit SQLCLR durchgeführt werden und erfordert einige Arbeit, um sie im System zu implementieren.
Ich habe die Komprimierung ausprobiert, und da XMLs hochgradig redundant sind, kann ich XML (in einer ac # -App) von 20 KB auf ~ 2,5 KB komprimieren und in der Spalte VARBINARY speichern, wodurch die Verwendung von LOB-Datenseiten verhindert wird. Dies beschleunigt SELECTs in meinen Tests um das 20-fache.
quelle
SELECT *
ist nicht das Problem, wenn Sie die XML-Daten benötigen. Es ist nur dann ein Problem, wenn Sie die XML-Daten nicht benötigen. In diesem Fall sollten Sie die Abfrage verlangsamen, um nicht verwendete Daten zurückzugewinnen. Ich fragte nach den Aktualisierungen des XML-Codes und fragte mich, ob die Fragmentierung auf den LOB-Seiten nicht korrekt gemeldet wurde. Aus diesem Grund hatte ich in meiner Antwort gefragt, wie genau Sie festgestellt haben, dass der Clustered-Index nicht fragmentiert ist. Können Sie den Befehl bereitstellen, den Sie ausgeführt haben? Und haben Sie den Clustered Index vollständig überarbeitet? (Fortsetzung)Antworten:
Nur die XML-Spalte in der Tabelle zu haben, hat diesen Effekt nicht. Aufgrund des Vorhandenseins von XML- Daten wird unter bestimmten Umständen ein Teil der Daten einer Zeile außerhalb der Zeile auf LOB_DATA-Seiten gespeichert. Und während einer (oder vielleicht mehrere ;-) argumentieren, dass duh,
XML
impliziert die Spalte, dass es tatsächlich XML-Daten geben wird, kann nicht garantiert werden, dass die XML-Daten außerhalb der Zeile gespeichert werden müssen: es sei denn, die Zeile ist so gut wie bereits gefüllt Abgesehen davon, dass es sich um XML-Daten handelt, passen kleine Dokumente (bis zu 8000 Byte) möglicherweise in eine Zeile und werden niemals zu einer LOB_DATA-Seite weitergeleitet.Das Scannen bezieht sich auf das Betrachten aller Zeilen. Wenn eine Datenseite gelesen wird, werden natürlich alle In-Row- Daten gelesen, auch wenn Sie eine Teilmenge der Spalten ausgewählt haben. Der Unterschied zu LOB-Daten besteht darin, dass die Daten außerhalb der Zeile nicht gelesen werden, wenn Sie diese Spalte nicht auswählen. Daher ist es nicht wirklich fair, eine Schlussfolgerung darüber zu ziehen, wie effizient SQL Server diesen Clustered Index scannen kann, da Sie dies nicht genau getestet haben (oder die Hälfte davon getestet haben). Sie haben alle Spalten ausgewählt, einschließlich der XML-Spalte. Wie Sie bereits erwähnt haben, befinden sich dort die meisten Daten.
Wir wissen also bereits, dass der
SELECT TOP 1000 *
Test nicht nur eine Reihe von 8.000 Datenseiten in einer Reihe las, sondern stattdessen zu anderen Stellen pro Reihe sprang . Die genaue Struktur dieser LOB-Daten kann je nach Größe variieren. Basierend auf den hier gezeigten Untersuchungen ( Wie groß ist der LOB-Zeiger für (MAX) -Typen wie Varchar, Varbinary usw.? ) Gibt es zwei Arten von Off-Row-LOB-Zuweisungen:Eine dieser beiden Situationen tritt jedes Mal auf, wenn Sie LOB-Daten abrufen, die größer als 8000 Bytes sind oder einfach nicht in die Zeile passen. Ich habe ein Testskript auf PasteBin.com veröffentlicht ( T-SQL-Skript zum Testen von LOB-Zuweisungen und -Lesevorgängen ), in dem die drei Arten von LOB-Zuweisungen (basierend auf der Größe der Daten) sowie deren Auswirkungen auf logische und angezeigt werden physische liest. Wenn in Ihrem Fall die XML-Daten tatsächlich weniger als 42.000 Byte pro Zeile umfassen, sollte sich keine (oder nur sehr wenige) davon in der am wenigsten effizienten TEXT_TREE-Struktur befinden.
Wenn Sie testen wollen , wie schnell SQL Server scannen , dass Clustered Index, tun das
SELECT TOP 1000
aber geben Sie eine oder mehrere Spalten nicht mit dieser XML - Spalte. Wie wirkt sich das auf Ihre Ergebnisse aus? Es sollte ziemlich viel schneller sein.Da wir eine unvollständige Beschreibung der tatsächlichen Tabellenstruktur und des Datenmusters haben, ist eine Antwort möglicherweise nicht optimal, abhängig von den fehlenden Details. In diesem Sinne würde ich sagen, dass Ihre Tabellenstruktur oder Ihr Datenmuster offensichtlich nichts Unangemessenes ist.
Das hat das Auswählen aller Spalten oder sogar nur der XML-Daten (jetzt in
VARBINARY
) beschleunigt, aber es schadet tatsächlich Abfragen, bei denen die "XML" -Daten nicht ausgewählt werden. Angenommen, Sie haben ungefähr 50 Bytes in den anderen Spalten und haben einenFILLFACTOR
Wert von 100, dann:Keine Komprimierung: Für
XML
15 KB Daten sollten 2 LOB_DATA-Seiten erforderlich sein, für die dann 2 Zeiger für den Inline-Stamm erforderlich sind. Der erste Zeiger ist 24 Bytes und der zweite 12 Bytes für insgesamt 36 Bytes, die in Reihe für die XML-Daten gespeichert sind. Die Gesamtzeilengröße beträgt 86 Byte, und Sie können ungefähr 93 dieser Zeilen auf eine 8060-Byte-Datenseite einfügen. Daher erfordert 1 Million Zeilen 10.753 Datenseiten.Benutzerdefinierte Komprimierung: 2,5 KB
VARBINARY
Daten passen in eine Zeile. Die Gesamtzeilengröße beträgt 2610 (2,5 * 1024 = 2560) Byte, und Sie können nur 3 dieser Zeilen auf eine 8060-Byte-Datenseite einfügen. Daher erfordert 1 Million Zeilen 333.334 Datenseiten.Ergo führt die Implementierung einer benutzerdefinierten Komprimierung zu einer 30-fachen Erhöhung der Datenseiten für den Clustered Index. Das bedeutet, dass bei allen Abfragen, die einen Clustered-Index-Scan verwenden, jetzt etwa 322.500 weitere Datenseiten zu lesen sind. Weitere Informationen zu dieser Art der Komprimierung finden Sie im folgenden Abschnitt.
Ich würde davor warnen, Refactoring auf der Grundlage der Leistung von durchzuführen
SELECT TOP 1000 *
. Dies ist wahrscheinlich keine Abfrage, die von der Anwendung selbst ausgegeben wird, und sollte nicht als einzige Grundlage für möglicherweise unnötige Optimierungen verwendet werden.Weitere Informationen und Tests finden Sie im folgenden Abschnitt.
Diese Frage kann nicht definitiv beantwortet werden, aber wir können zumindest einige Fortschritte erzielen und zusätzliche Untersuchungen vorschlagen, um uns dem genauen Problem zu nähern (im Idealfall basierend auf Beweisen).
Was wir wissen:
XML
Spalte und mehrere andere Spalten der Typen:INT
,BIGINT
,UNIQUEIDENTIFIER
, „etc.“XML
Die "Spaltengröße" beträgt im Durchschnitt ca. 15 KBDBCC DROPCLEANBUFFERS
dauert es 20 bis 25 Sekunden, bis die folgende Abfrage abgeschlossen ist:SELECT TOP 1000 * FROM TABLE
Was wir zu wissen glauben:
XML-Komprimierung könnte helfen. Wie genau würden Sie die Komprimierung in .NET durchführen? Über die Klassen GZipStream oder DeflateStream ? Dies ist keine kostenfreie Option. Es wird sicherlich einige der Daten um einen großen Prozentsatz komprimieren, aber es wird auch mehr CPU erfordern, da Sie jedes Mal einen zusätzlichen Prozess zum Komprimieren / Dekomprimieren der Daten benötigen. Mit diesem Plan können Sie auch Folgendes vollständig entfernen:
.nodes
,.value
,.query
und.modify
XML - Funktionen.Indizieren Sie die XML-Daten.
Beachten Sie (da Sie erwähnt haben, dass XML "hochredundant" ist), dass der
XML
Datentyp bereits dahingehend optimiert ist, dass er die Element- und Attributnamen in einem Wörterbuch speichert, jedem Element eine ganzzahlige Index-ID zuweist und dann diese ganzzahlige ID verwendet im gesamten Dokument (daher wiederholt es nicht den vollständigen Namen für jede Verwendung, noch wiederholt es ihn erneut als abschließendes Tag für Elemente). Bei den eigentlichen Daten wurden auch externe Leerzeichen entfernt. Dies ist der Grund, warum extrahierte XML-Dokumente ihre ursprüngliche Struktur nicht beibehalten und warum leere Elemente so extrahiert werden, als<element />
ob sie als eingefügt worden wären<element></element>
. Alle Vorteile der Komprimierung über GZip (oder etwas anderes) werden also nur durch die Komprimierung der Element- und / oder Attributwerte erzielt. Dies ist eine viel kleinere Oberfläche, die verbessert werden könnte, als die meisten dies erwarten würden und deren Verlust sich wahrscheinlich nicht lohnt Fähigkeiten wie direkt oben angegeben.Beachten Sie bitte auch, dass durch das Komprimieren der XML-Daten und das Speichern des
VARBINARY(MAX)
Ergebnisses der LOB-Zugriff nicht aufgehoben, sondern nur reduziert wird. Abhängig von der Größe der restlichen Daten in der Zeile passt der komprimierte Wert möglicherweise in die Zeile oder erfordert weiterhin LOB-Seiten.Diese Informationen sind zwar hilfreich, aber bei weitem nicht genug. Es gibt eine Reihe von Faktoren, die die Abfrageleistung beeinflussen. Daher benötigen wir ein detaillierteres Bild der aktuellen Ereignisse.
Was wir nicht wissen, aber müssen:
SELECT *
Materie? Ist dies ein Muster, das Sie im Code verwenden. Wenn ja warum?SELECT TOP 1000 XmlColumn FROM TABLE;
:?Wie viel von den 20 bis 25 Sekunden, die für die Rückgabe dieser 1000 Zeilen benötigt werden, hängt mit den Netzwerkfaktoren (Abrufen der Daten über die Leitung) zusammen und wie viel mit den Client-Faktoren (Rendern von ca. 15 MB plus dem Rest der nicht zur Verfügung stehenden Daten). XML-Daten in das Grid in SSMS (oder möglicherweise auf Festplatte speichern)?
Das Ausklammern dieser beiden Aspekte des Vorgangs kann manchmal erfolgen, indem die Daten einfach nicht zurückgegeben werden. Man könnte nun überlegen, eine temporäre Tabelle oder eine Tabellenvariable auszuwählen, aber dies würde nur ein paar neue Variablen einführen (z. B. Datenträger-E / A für
tempdb
Transaktionsprotokoll-Schreibvorgänge, mögliches automatisches Wachstum von Tempdb-Daten und / oder Protokolldateien) Platz im Buffer Pool, etc). All diese neuen Faktoren können die Abfragezeit tatsächlich verlängern. Stattdessen speichere ich die Spalten normalerweise in Variablen (des entsprechenden Datentyps; nichtSQL_VARIANT
), die mit jeder neuen Zeile (dhSELECT @Column1 = tab.Column1,...
) überschrieben werden .JEDOCH , wie von @PaulWhite in diesem DBA.StackExchange Q & A wies darauf hin, Logik liest anders , wenn die gleichen LOB - Daten zugreifen , mit zusätzlicher Forschung meiner eigenen geschrieben auf Pastebin ( T-SQL - Skript verschiedene Szenarien zu testen , für LOB liest ) , LOB konsistent zwischen nicht zugegriffen
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
, undSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Daher sind unsere Optionen hier etwas eingeschränkt, aber hier ist, was getan werden kann:Alternativ können Sie die Abfrage über sqlcmd.exe ausführen und die Ausgabe zu nirgendwo gehen direkt über:
-o NUL:
.Was ist die tatsächliche Datengröße für die
XML
Spalten zurückgegeben werden ? Die durchschnittliche Größe dieser Spalte in der gesamten Tabelle spielt keine Rolle, wenn die "TOP 1000" -Zeilen einen unverhältnismäßig großen Teil der GesamtdatenXML
enthalten. Wenn Sie mehr über die TOP 1000-Zeilen erfahren möchten, schauen Sie sich diese Zeilen an. Bitte führen Sie Folgendes aus:CREATE TABLE
Erklärung einschließlich aller Indizes an.Was sind die genauen Ergebnisse der folgenden Abfrage:
AKTUALISIEREN
Mir fiel ein, dass ich versuchen sollte, dieses Szenario zu reproduzieren, um festzustellen, ob ich ein ähnliches Verhalten habe. Also habe ich eine Tabelle mit mehreren Spalten erstellt (ähnlich der vagen Beschreibung in der Frage) und sie dann mit 1 Million Zeilen aufgefüllt. Die XML-Spalte enthält ungefähr 15 KB Daten pro Zeile (siehe Code unten).
Was ich gefunden habe, ist, dass
SELECT TOP 1000 * FROM TABLE
das erste Mal in 8 Sekunden und danach jedes Mal 2 - 4 Sekunden abgeschlossen wurden (ja, wirdDBCC DROPCLEANBUFFERS
vor jedem Durchlauf derSELECT *
Abfrage ausgeführt). Und mein mehrere Jahre alter Laptop ist nicht schnell: SQL Server 2012 SP2 Developer Edition, 64-Bit, 6 GB RAM, dualer 2,5-GHz-Core i5 und ein SATA-Laufwerk mit 5400 U / min. Ich verwende auch SSMS 2014, SQL Server Express 2014, Chrome und einige andere Dinge.Aufgrund der Reaktionszeit meines Systems möchte ich wiederholen, dass wir weitere Informationen benötigen (z. B. Angaben zu Tabelle und Daten, Ergebnisse der vorgeschlagenen Tests usw.), um die Ursache für die Reaktionszeit von 20 bis 25 Sekunden einzugrenzen dass du siehst.
Und weil wir die Zeit herausrechnen möchten, die zum Lesen der Nicht-LOB-Seiten benötigt wird, habe ich die folgende Abfrage ausgeführt, um alle außer der XML-Spalte auszuwählen (einer der oben vorgeschlagenen Tests). Dies kehrt ziemlich konsequent in 1,5 Sekunden zurück.
Fazit (für den Moment)
Aufgrund meines Versuchs, Ihr Szenario neu zu erstellen, können wir weder auf das SATA-Laufwerk noch auf nicht-sequentielle E / A als Hauptursache für die 20 bis 25 Sekunden verweisen, vor allem, weil wir dies immer noch tun Ich weiß nicht, wie schnell die Abfrage zurückkehrt, wenn die XML-Spalte nicht enthalten ist. Und ich war nicht in der Lage, die große Anzahl von Logical Reads (Nicht-LOB) zu reproduzieren, die Sie anzeigen, aber ich habe das Gefühl, dass ich in Anbetracht dessen und der folgenden Aussage mehr Daten zu jeder Zeile hinzufügen muss :
Meine Tabelle enthält 1 Million Zeilen mit jeweils etwas mehr als 15.000 XML-Daten und
sys.dm_db_index_physical_stats
zeigt, dass es 2 Millionen LOB_DATA-Seiten gibt. Die restlichen 10% wären dann 222.000 IN_ROW-Datenseiten, von denen ich jedoch nur 11.630 habe. Wir brauchen also noch einmal mehr Informationen über das aktuelle Tabellenschema und die aktuellen Daten.quelle
Ja, das Lesen von LOB-Daten, die nicht in Reihe gespeichert sind, führt zu zufälligen E / A-Vorgängen anstelle von sequentiellen E / A-Vorgängen. Die hier zu verwendende Kennzahl für die Festplattenleistung, um zu verstehen, warum sie schnell oder langsam ist, ist Random Read IOPS.
LOB-Daten werden in einer Baumstruktur gespeichert, in der die Datenseite im Clustered-Index auf eine LOB-Datenseite mit einer LOB-Stammstruktur verweist, die wiederum auf die tatsächlichen LOB-Daten verweist. Beim Durchlaufen der Stammknoten im Clustered-Index kann SQL Server die In-Row-Daten nur durch sequentielle Lesevorgänge abrufen. Um die LOB-Daten zu erhalten, muss SQL Server an einer anderen Stelle auf der Festplatte abgelegt werden.
Ich schätze, wenn Sie auf eine SSD-Festplatte wechseln würden, würden Sie nicht so stark darunter leiden, da zufällige IOPS für eine SSD viel höher sind als für eine sich drehende Festplatte.
Ja, das könnte sein. Kommt darauf an, was dieser Tisch für Sie tut.
In der Regel treten die Leistungsprobleme bei XML in SQL Server auf, wenn Sie T-SQL zum Abfragen in XML verwenden möchten, und noch mehr, wenn Sie Werte aus XML in einem Prädikat in einer where-Klausel oder einem Join verwenden möchten. Wenn das der Fall ist , könnten Sie einen Blick auf haben Eigenschaftenheraufstufung oder selektiven XML - Indizes oder ein Redesign Ihrer Tabellenstrukturen , anstatt die XML - Tabellen Schredder.
Ich habe das vor etwas mehr als 10 Jahren einmal in einem Produkt getan und es seitdem bereut. Ich habe wirklich vermisst, dass ich mit T-SQL nicht mit den Daten arbeiten kann, deshalb würde ich das niemandem empfehlen, wenn es vermieden werden kann.
quelle