Schlechte Leistung der Zeittabelle bei älteren Werten


8

Beim Zugriff auf historische Datensätze in einer Zeittabelle tritt ein seltsames Problem auf. Abfragen, die über die AS OF-Unterklausel auf die älteren Einträge in der Zeittabelle zugreifen, dauern länger als Abfragen zu aktuellen historischen Einträgen.

Die Verlaufstabelle wurde von SQL Server generiert (enthält einen Clustered-Index für die Datumsspalten und verwendet die Seitenkomprimierung). Ich habe der Verlaufstabelle 50 Millionen Zeilen hinzugefügt und meine Abfragen haben ungefähr 25.000 Zeilen abgerufen.

Ich habe versucht, die Grundursache des Problems zu ermitteln, konnte es jedoch nicht identifizieren. Bisher habe ich getestet:

  • Erstellen einer Testtabelle mit 50 Millionen Zeilen mit einem Clustered-Index, um festzustellen, ob die Verlangsamung einfach auf das Volumen zurückzuführen ist. Ich konnte 25K Zeilen zu konstanter Zeit (~ 400ms) abrufen.
  • Entfernen der Seitenkomprimierung aus der Verlaufstabelle. Dies hatte keinen Einfluss auf die Abrufzeit, erhöhte jedoch die Größe der Tabelle erheblich.
  • Ich habe versucht, direkt auf die Zeilen der Verlaufstabelle zuzugreifen, indem ich eine ID-Spalte gegenüber den Datumsspalten verwendet habe. Hier waren die Dinge etwas interessanter. Ich konnte mit ~ 400 ms auf ältere Zeilen in der Tabelle zugreifen, wobei es wie bei der AS OF-Unterklausel ~ 1200 ms dauern würde. Ich habe versucht, in meiner Testtabelle in der Datumsspalte zu filtern, und habe eine ähnliche Verlangsamung im Vergleich zum Filtern in der ID-Spalte festgestellt. Dies lässt mich glauben, dass die Datumsvergleiche hinter einem Teil der Verlangsamung stecken.

Ich möchte mir das genauer ansehen, aber ich möchte auch sicherstellen, dass ich nicht den falschen Baum belle. Hat jemand anderes das gleiche Verhalten beim Zugriff auf ältere historische Daten in einer Zeittabelle festgestellt (wir haben nur festgestellt, dass Verlangsamungen 10 Millionen Zeilen überschritten haben)? Zweitens, mit welchen Strategien kann ich die Hauptursache des Leistungsproblems weiter eingrenzen (ich habe gerade angefangen, Ausführungspläne zu untersuchen, aber es ist für mich immer noch etwas kryptisch)?

Ausführungspläne

Dies sind einfache Abrufabfragen: Die erste greift auf ältere Zeilen zu, die zweite auf neuere Zeilen.

Ältere Zeilen ~ 1200 ms Ausführungszeit

Letzte Zeilen ~ 350 ms Ausführungszeit

Tabellendetails

Dies sind die Spalten in der Zeittabelle. Die Verlaufstabelle hat dieselben Spalten, jedoch keinen Primärschlüssel (gemäß den Anforderungen der Verlaufstabelle): Zeittabellenspalte

Nachfolgend sind die Indizes in der Verlaufstabelle aufgeführt: Indizes in der Verlaufstabelle

Antworten:


6

In einem Kommentar von Zane zu Ihrer Frage erklärte er:

... Es scheint, als ob ein Teil Ihres Problems darin besteht, 50 Millionen Zeilen zu lesen, um 20.000 im Plan zurückzugeben.

Dies ist in der Tat das Problem. Es ist kein Index verfügbar, um einige oder alle Prädikate auf die Speicher-Engine zu übertragen. Microsoft empfiehlt diese Basisindizierungsstrategie für temporäre Tabellen im Docs-Artikel Überlegungen und Einschränkungen zu temporalen Tabellen :

Eine optimale Indizierungsstrategie umfasst einen Clustered Column Store-Index und / oder einen B-Tree-Rowstore-Index für die aktuelle Tabelle und einen Clustered Columnstore-Index für die Verlaufstabelle für eine optimale Speichergröße und Leistung. Wenn Sie eine eigene Verlaufstabelle erstellen / verwenden, empfehlen wir dringend, diesen Indextyp zu erstellen, der aus Periodenspalten besteht, die mit der Spalte zum Ende der Periode beginnen, um die zeitliche Abfrage sowie die Abfragen zu beschleunigen, die Teil der Datenkonsistenz sind prüfen. In der Standardverlaufstabelle wird ein Clustered Rowstore-Index erstellt, der basierend auf den Periodenspalten (Ende, Start) für Sie erstellt wird. Es wird mindestens ein nicht gruppierter Rowstore-Index empfohlen

Die Formulierung ist etwas verwirrend (für mich jedenfalls). Zum Mitnehmen können Sie diese Indizes jedoch erstellen, um die Leistung zu verbessern, wenn nicht sogar erheblich:

NC-Index für die aktuelle Tabelle, führend mit SysEndTime:

CREATE NONCLUSTERED INDEX IX_SysEndTime_SysStartTime 
ON dbo.Benefits (SysEndTime, SysStartTime)
/*INCLUDE (ideally, include your other important fields here)*/;

Auf diese Weise können Sie vermeiden, einige der Zeilen in der aktuellen Tabelle zu lesen, indem Sie nach der entsprechenden Endzeit suchen.

CCI in der Verlaufstabelle

CREATE CLUSTERED COLUMNSTORE INDEX ix_BenefitsHistory
ON dbo.BenefitsHistory
WITH (DROP_EXISTING = ON);

Auf diese Weise erhalten Sie den Stapelmodus für die Verlaufstabelle, wodurch die Scans viel schneller werden sollten.

NC-Index für die aktuelle Tabelle, führend mit SysStartTime:

Weitere Informationen dazu, warum die Indizierung für Datumsbereichsabfragen schwierig ist, finden Sie in Pauls Antwort auf die Frage " Effizienteste Methode zum Abrufen von Datumsbereichen" . Basierend auf der dortigen Logik ist es sinnvoll, der aktuellen Tabelle, die mit SysStartTime führt, einen weiteren NC-Index hinzuzufügen, damit der Optimierer anhand der Statistiken und der spezifischen Parameter Ihrer Abfrage auswählen kann, welcher verwendet werden soll:

CREATE NONCLUSTERED INDEX IX_SysStartTime_SysEndTime
ON dbo.Benefits (SysStartTime, SysEndTime)
/*INCLUDE (ideally, include your other important fields here)*/;

Das Erstellen der drei oben beschriebenen Indizes hat in meinen Testfällen einen signifikanten Unterschied in der Ressourcennutzung bewirkt. Ich habe einen Testfall eingerichtet, in dem zwei Abfragen ausgeführt werden, die insgesamt 1,5 Millionen Zeilen zurückgeben. Sowohl die Verlaufs- als auch die aktuellen Tabellen haben 50 Millionen Zeilen.

Hinweis: Um den SSMS-Overhead zu reduzieren, habe ich den Test mit aktivierter Option "Ergebnisse nach Ausführung verwerfen" ausgeführt.

Ausführungsplan - Standardindizes

Logische Lesevorgänge: 1.330.612
CPU-Zeit: 00: 00: 14.718
Verstrichene Zeit: 00: 00: 06.198

Ausführungsplan - Mit den oben beschriebenen Indizes

Logische Lesevorgänge: 27.656 (8.111 Zeilenspeicher + 19.545 Spaltenspeicher)
CPU-Zeit: 00: 00: 01.828
Verstrichene Zeit: 00: 00: 01.150

Wie Sie sehen können, sind alle drei Maßnahmen erheblich gesunken - einschließlich der insgesamt verstrichenen Zeit von 6 Sekunden auf 1 Sekunde.


Die andere Option, die im Docs-Artikel vorgestellt wird, besteht darin, auf die beiden NC-Indizes in der aktuellen Tabelle zugunsten eines Clustered Columnstore-Index zu verzichten. In meinem Test war die Leistung der oben beschriebenen Indexierungslösung sehr ähnlich.


2

Die FOR SYSTEM TIME AS OFKlausel versucht, das zum angegebenen Zeitpunkt vorhandene Dataset zurückzugeben. Dies bedeutet, dass Aktualisierungen intern rückgängig gemacht werden müssen, Löschvorgänge nicht wiederhergestellt werden müssen und Einfügungen basierend auf der Systemzeit der Anforderung ignoriert werden müssen.

Je weiter in der Vergangenheit die AS OF-Zeit liegt, desto mehr Arbeit muss validiert werden, um sicherzustellen, dass die Zeittabelle zum angegebenen Systemzeitpunkt vorhanden ist, und desto länger dauert die Abfrage.

WENN die Datentabelle nur eine Protokollierungstabelle ist und keine Änderungen an den Daten vorgenommen werden, werden die Daten anhand des protokollierten Datums und eines Index schneller und konsistenter zurückgegeben. Ob in diesem Fall die zeitlichen Merkmale verwendet werden sollen, ist nicht erforderlich. Wenn jedoch Änderungen an den Zeilen vorgenommen werden (außer Einfügungen), ist die Verwendung der Funktion für die temporäre Tabelle die einzige Möglichkeit, die genauen angeforderten Daten zurückzugeben (den Status der Tabelle, wie er zu diesem bestimmten Zeitpunkt vorhanden war) Sie müssen nur den zusätzlichen Aufwand für die zeitlichen Abfragen akzeptieren.

Hinweis: Die "Rollbacks" sind keine tatsächlichen Rollbacks. Zeitliche Tabellen verwenden zwei Tabellen - eine aktuelle Tabelle und eine Verlaufstabelle. Wenn eine Zeile geändert wird, wird eine Kopie der vorherigen Version mit dem Zeitbereich, in dem die Zeile gültig war, in die Verlaufstabelle eingefügt. Wenn Sie eine Zeile um 10/20/2018 10: 20: 20.18 einfügen, aktualisieren Sie einen Wert um 10/25/2018 10: 25: 20.18 und aktualisieren Sie ihn erneut um 12/01/2018 12: 01: 20.18 die neueste Version der Zeile in der aktuellen Tabelle mit einem Startdatum von 12/01/2018 12: 01: 20.18 und zwei Zeilen in der Verlaufstabelle mit gültigen Bereichen von 10/20 bis 10/25/2018 und 10 / 25 bis 12/01/2018


Danke für die Antwort! Das ist auf jeden Fall intuitiv sinnvoll, aber ich habe in den von mir gelesenen Dokumenten keine Erwähnung dieser Art von Verhalten gefunden (ich habe nur die Grundlagen der Zeittabelle in den Dokumenten von MS durchgearbeitet). Kennen Sie eine Dokumentation, die das Verhalten etwas detaillierter beschreibt?
Ebrahim Behbahani
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.