Diese Frage ähnelt der Optimierung der IP-Bereichssuche? dieser ist jedoch auf SQL Server 2000 beschränkt.
Angenommen, ich habe 10 Millionen Bereiche vorläufig in einer Tabelle gespeichert, die wie folgt strukturiert und ausgefüllt ist.
CREATE TABLE MyTable
(
Id INT IDENTITY PRIMARY KEY,
RangeFrom INT NOT NULL,
RangeTo INT NOT NULL,
CHECK (RangeTo > RangeFrom),
INDEX IX1 (RangeFrom,RangeTo),
INDEX IX2 (RangeTo,RangeFrom)
);
WITH RandomNumbers
AS (SELECT TOP 10000000 ABS(CRYPT_GEN_RANDOM(4)%100000000) AS Num
FROM sys.all_objects o1,
sys.all_objects o2,
sys.all_objects o3,
sys.all_objects o4)
INSERT INTO MyTable
(RangeFrom,
RangeTo)
SELECT Num,
Num + 1 + CRYPT_GEN_RANDOM(1)
FROM RandomNumbers
Ich muss alle Bereiche kennen, die den Wert enthalten 50,000,000
. Ich versuche die folgende Abfrage
SELECT *
FROM MyTable
WHERE 50000000 BETWEEN RangeFrom AND RangeTo
SQL Server zeigt, dass es 10.951 logische Lesevorgänge gab und fast 5 Millionen Zeilen gelesen wurden, um die 12 übereinstimmenden zurückzugeben.
Kann ich diese Leistung verbessern? Eine Umstrukturierung der Tabelle oder zusätzlicher Indizes ist in Ordnung.
sql-server
optimization
Martin Smith
quelle
quelle
contains
Abfragen unterstützen und obwohl sie gut daran arbeiten, die gelesene Datenmenge zu reduzieren, scheinen sie andere hinzuzufügen Aufwand, der dem entgegenwirkt.Antworten:
Columnstore ist hier sehr hilfreich im Vergleich zu einem nicht gruppierten Index, der die Hälfte der Tabelle durchsucht. Ein nicht gruppierter Columnstore-Index bietet den größten Vorteil, aber das Einfügen geordneter Daten in einen gruppierten Columnstore-Index ist noch besser.
Durch das Design kann ich Zeilengruppen in der
RangeFrom
Spalte entfernen, wodurch die Hälfte meiner Zeilengruppen entfernt wird. Aufgrund der Art der Daten erhalte ich aber auch eine Zeilengruppen-Eliminierung für dieRangeTo
Spalte:Für größere Tabellen mit mehr variablen Daten gibt es verschiedene Möglichkeiten, die Daten zu laden, um die bestmögliche Eliminierung von Zeilengruppen in beiden Spalten zu gewährleisten. Insbesondere für Ihre Daten dauert die Abfrage 1 ms.
quelle
Paul White wies auf eine Antwort auf eine ähnliche Frage hin, die einen Link zu einem interessanten Artikel von Itzik Ben Gan enthielt . Dies beschreibt das Modell "Static Relational Interval Tree", mit dem dies effizient durchgeführt werden kann.
Zusammenfassend beinhaltet dieser Ansatz das Speichern eines berechneten ("forknode") Wertes basierend auf den Intervallwerten in der Zeile. Wenn Sie nach Bereichen suchen, die einen anderen Bereich überschneiden, können Sie die möglichen forknode-Werte vorberechnen, die für übereinstimmende Zeilen erforderlich sind, und auf diese Weise die Ergebnisse mit maximal 31 Suchoperationen ermitteln (im Folgenden werden Ganzzahlen im Bereich von 0 bis maximal vorzeichenbehafteten 32 unterstützt) bit int)
Auf dieser Grundlage habe ich die Tabelle wie folgt umstrukturiert.
Und dann die folgende Abfrage verwendet (der Artikel sucht nach sich überschneidenden Intervallen, so dass das Finden eines Intervalls, das einen Punkt enthält, ein entarteter Fall ist)
Dies wird normalerweise
1ms
auf meinem Computer ausgeführt, wenn sich alle Seiten im Cache befinden - mit E / A-Statistiken.und planen
NB: Die Quelle verwendet Multistatement-TVFs anstelle eines rekursiven CTE, um die Knoten zum Beitritt zu bewegen, aber um meine Antwort in sich geschlossen zu halten, habe ich mich für Letzteres entschieden. Für die Produktion würde ich wahrscheinlich die TVFs verwenden.
quelle
Ich konnte einen Zeilenmodus-Ansatz finden, der mit dem N / CCI-Ansatz konkurriert, aber Sie müssen etwas über Ihre Daten wissen. Angenommen, Sie hatten eine Spalte, die den Unterschied von
RangeFrom
und enthielt,RangeTo
und Sie indizierten sie zusammen mitRangeFrom
:Wenn Sie alle unterschiedlichen Werte von kennen, können
DiffOfColumns
SieDiffOfColumns
mit einem Bereichsfilter nach jedem Wert von suchenRangeTo
, um alle relevanten Daten abzurufen. Wenn wir zum Beispiel wissen, dassDiffOfColumns
= 2 ist, sind die einzigen zulässigen Werte fürRangeFrom
49999998, 49999999 und 50000000. Mit der Rekursion können alle eindeutigen Werte von ermittelt werden,DiffOfColumns
und dies funktioniert gut für Ihren Datensatz, da es nur 256 davon gibt. Die folgende Abfrage dauert auf meinem Computer ca. 6 ms:Sie können den üblichen rekursiven Teil zusammen mit der Indexsuche für jeden einzelnen Wert sehen:
Der Fehler bei diesem Ansatz ist, dass es langsam wird, wenn es zu viele unterschiedliche Werte für gibt
DiffOfColumns
. Lass uns den gleichen Test machen, aberCRYPT_GEN_RANDOM(2)
stattdessen verwendenCRYPT_GEN_RANDOM(1)
.Dieselbe Abfrage findet jetzt 65536 Zeilen aus dem rekursiven Teil und beansprucht 823 ms CPU auf meinem Computer. Es gibt PAGELATCH_SH Wartezeiten und andere schlimme Dinge. Ich kann die Leistung verbessern, indem ich die Diff - Werte sammle, um die Anzahl der eindeutigen Werte unter Kontrolle zu halten, und das Sammeln in anpasse
CROSS APPLY
. Für diesen Datensatz versuche ich 256 Eimer:Eine Möglichkeit, zusätzliche Zeilen zu vermeiden (jetzt vergleiche ich mit einem gerundeten Wert anstelle des wahren Werts), besteht darin, Folgendes zu filtern
RangeTo
:Die vollständige Abfrage auf meinem Computer dauert jetzt 6 ms.
quelle
Eine alternative Möglichkeit, einen Bereich darzustellen, wären Punkte auf einer Linie.
Das Folgende migriert alle Daten in eine neue Tabelle, wobei der Bereich als
geometry
Datentyp dargestellt wird.Die entsprechende Abfrage zum Suchen von Bereichen, die den Wert enthalten, finden Sie
50,000,000
unten.Die Reads dafür zeigen eine Verbesserung
10,951
gegenüber der ursprünglichen Abfrage.Es gibt jedoch keine signifikante Verbesserung gegenüber dem Original in Bezug auf die verstrichene Zeit . Typische Ausführungsergebnisse sind 250 ms gegenüber 252 ms.
Der Ausführungsplan ist komplexer als unten
Der einzige Fall, in dem das Neuschreiben für mich verlässlich besser funktioniert, ist ein kalter Cache.
Daher ist es in diesem Fall enttäuschend und schwierig, dieses Umschreiben zu empfehlen, aber die Veröffentlichung negativer Ergebnisse kann ebenfalls nützlich sein.
quelle
Als Hommage an unsere neuen Roboter-Overlords habe ich mich entschieden, ob uns eine der neuen R- und Python-Funktionen hier weiterhelfen kann. Die Antwort ist nein, zumindest für die Skripte, mit denen ich arbeiten konnte und die korrekte Ergebnisse liefern. Wenn jemand mit besseren Kenntnissen vorbeikommt, zögern Sie nicht, mich zu verprügeln. Meine Preise sind angemessen.
Zu diesem Zweck habe ich eine VM mit 4 Kernen und 16 GB RAM eingerichtet, da ich dachte, dies würde ausreichen, um mit einem Datensatz von ~ 200 MB fertig zu werden.
Beginnen wir mit der Sprache, die es in Boston nicht gibt!
R
Das war eine schlechte Zeit.
Der Ausführungsplan ist ziemlich uninteressant, obwohl ich nicht weiß, warum der mittlere Operator uns Namen nennen muss.
Als nächstes wird mit Buntstiften codiert!
Python
Gerade als Sie dachten, es könnte nicht schlimmer werden als R:
Ein weiterer Exekutionsplan mit schlechtem Mund :
Hmm und Hmmer
Bisher bin ich nicht beeindruckt. Ich kann es kaum erwarten, diese VM zu löschen.
quelle
DECLARE @input INT = 50000001; EXEC dbo.sp_execute_external_script @language = N'R', @script = N'OutputDataSet <- InputDataSet[which(x >= InputDataSet$RangeFrom & x <= InputDataSet$RangeTo) , ]', @parallel = 1, @input_data_1 = N'SELECT Id, RangeFrom, RangeTo FROM dbo.MyTable;', @params = N'@x INT', @x = 50000001 WITH RESULT SETS ( ( Id INT NOT NULL, RangeFrom INT NOT NULL, RangeTo INT NOT NULL ));
aber die Leistung ist nicht großartig. Ich benutze R für Dinge, die Sie in SQL nicht tun können, sagen Sie, wenn Sie etwas vorhersagen wollten.Ich habe eine ziemlich gute Lösung mit einer berechneten Spalte gefunden, die jedoch nur für einen einzelnen Wert geeignet ist. Davon abgesehen, wenn Sie einen magischen Wert haben, ist es vielleicht genug.
Beginnen Sie mit Ihrem vorgegebenen Beispiel und ändern Sie dann die Tabelle:
Die Abfrage wird einfach zu:
Welches die gleichen Ergebnisse wie Ihre Startabfrage zurückgibt. Mit deaktivierten Ausführungsplänen sind hier die Statistiken (der Kürze halber abgeschnitten):
Und hier ist der Abfrageplan :
quelle
WHERE (50000000 BETWEEN RangeFrom AND RangeTo) INCLUDE (..)
?CREATE INDEX IX1_redo ON dbo.MyTable (curtis_jackson) INCLUDE (RangeFrom, RangeTo) WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000
würde funktionieren. Und die AbfrageSELECT * FROM MyTable WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000;
verwendet es - also nicht viel für den armen CurtisMeine Lösung basiert auf der Beobachtung, dass das Intervall eine bekannte maximale Breite W hat . Für die Beispieldaten ist dies ein Byte oder 256 Ganzzahlen. Daher wissen wir für einen gegebenen Suchparameterwert P , dass der kleinste RangeFrom, der in der Ergebnismenge enthalten sein kann, P - W ist . Wenn Sie dies zum Prädikat hinzufügen, erhalten Sie
Bei der ursprünglichen Einrichtung und Abfrage gibt mein Computer (64-Bit-Windows 10, 4-Core-Hyperthread-i7, 2,8 GHz, 16 GB RAM) 13 Zeilen zurück. Diese Abfrage verwendet eine parallele Indexsuche des Index (RangeFrom, RangeTo). Die überarbeitete Abfrage führt auch eine parallele Indexsuche für denselben Index durch.
Die Maße für die ursprünglichen und überarbeiteten Abfragen sind
Bei der ursprünglichen Abfrage entspricht die Anzahl der gelesenen Zeilen der Anzahl der Zeilen, die kleiner oder gleich @P sind. Der Abfrageoptimierer (QO) hat keine Alternative, sondern liest sie alle, da er nicht im Voraus bestimmen kann, welche Zeilen das Prädikat erfüllen sollen. Der mehrspaltige Index für (RangeFrom, RangeTo) ist nicht nützlich, um Zeilen zu entfernen, die nicht mit RangeTo übereinstimmen, da keine Korrelation zwischen dem ersten Indexschlüssel und dem zweiten Indexschlüssel besteht, der angewendet werden kann. Beispielsweise kann die erste Reihe ein kleines Intervall haben und beseitigt werden, während die zweite Reihe ein großes Intervall hat und zurückgegeben wird, oder umgekehrt.
In einem fehlgeschlagenen Versuch habe ich versucht, diese Sicherheit durch eine Prüfbedingung zu gewährleisten:
Es machte keinen Unterschied.
Indem ich mein externes Wissen über die Datenverteilung in das Prädikat einbeziehe, kann ich bewirken, dass die QO niedrigwertige RangeFrom-Zeilen, die niemals Teil der Ergebnismenge sein dürfen, überspringt und die führende Spalte des Index zu den zulässigen Zeilen durchläuft. Dies wird in dem unterschiedlichen Suchprädikat für jede Abfrage angezeigt.
In einem Spiegel - Argumente ist die obere Grenze der RangeTo P + W . Dies ist jedoch nicht sinnvoll, da es keine Korrelation zwischen RangeFrom und RangeTo gibt, die es der nachfolgenden Spalte eines mehrspaltigen Index ermöglicht, Zeilen zu entfernen. Daher ist es nicht vorteilhaft, diese Klausel zur Abfrage hinzuzufügen.
Dieser Ansatz profitiert vor allem von der geringen Intervallgröße. Wenn die mögliche Intervallgröße zunimmt, nimmt die Anzahl der übersprungenen Zeilen mit niedrigem Wert ab, obwohl einige weiterhin übersprungen werden. Im Grenzfall ist dieser Ansatz bei einem Intervall, das so groß ist wie der Datenbereich, nicht schlechter als die ursprüngliche Abfrage (die ich zugeben muss, ist ein kalter Trost).
Ich entschuldige mich für etwaige Fehler in dieser Antwort.
quelle