Cette question est similaire à Optimizing IP Range Search? mais celui-ci est limité à SQL Server 2000.
Supposons que j'ai 10 millions de plages stockées provisoirement dans un tableau structuré et rempli comme ci-dessous.
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
J'ai besoin de connaître toutes les plages contenant la valeur 50,000,000
. J'essaie la requête suivante
SELECT *
FROM MyTable
WHERE 50000000 BETWEEN RangeFrom AND RangeTo
SQL Server montre qu'il y a eu 10 951 lectures logiques et près de 5 millions de lignes ont été lues pour renvoyer les 12 correspondances.
Puis-je améliorer ces performances? Toute restructuration de la table ou des index supplémentaires est très bien.
sql-server
optimization
Martin Smith
la source
la source
contains
requêtes et bien qu'ils fonctionnent bien pour réduire la quantité de données lues, ils semblent ajouter d'autres frais généraux qui contrecarre cela.Réponses:
Columnstore est très utile ici par rapport à un index non cluster qui scanne la moitié du tableau. Un index columnstore non cluster offre la plupart des avantages, mais l'insertion de données ordonnées dans un index columnstore en cluster est encore meilleure.
Par conception, je peux obtenir l'élimination des groupes de lignes sur la
RangeFrom
colonne, ce qui éliminera la moitié de mes groupes de lignes. Mais en raison de la nature des données, j'obtiens également l'élimination des groupes de lignes sur laRangeTo
colonne:Pour les tables plus grandes avec plus de données variables, il existe différentes façons de charger les données pour garantir la meilleure élimination possible des groupes de lignes sur les deux colonnes. Pour vos données en particulier, la requête prend 1 ms.
la source
Paul White a indiqué une réponse à une question similaire contenant un lien vers un article intéressant d'Itzik Ben Gan . Ceci décrit le modèle "Static Relational Interval Tree" qui permet de le faire efficacement.
En résumé, cette approche implique le stockage d'une valeur calculée ("forknode") basée sur les valeurs d'intervalle dans la ligne. Lors de la recherche de plages qui croisent une autre plage, il est possible de précalculer les valeurs possibles de knode que les lignes correspondantes doivent avoir et de l'utiliser pour trouver les résultats avec un maximum de 31 opérations de recherche (le ci-dessous prend en charge les entiers compris entre 0 et le maximum signé 32). bit int)
Sur cette base, j'ai restructuré le tableau comme ci-dessous.
Et puis utilisé la requête suivante (l'article recherche des intervalles qui se croisent donc trouver un intervalle contenant un point en est un cas dégénéré)
Cela s'exécute généralement
1ms
sur ma machine lorsque toutes les pages sont dans le cache - avec les statistiques d'E / S.et planifier
NB: La source utilise des TVF multi-états plutôt qu'un CTE récursif pour obtenir la connexion des nœuds mais dans l'intérêt de rendre ma réponse autonome, j'ai opté pour ce dernier. Pour une utilisation en production, j'utiliserais probablement les TVF.
la source
J'ai pu trouver une approche en mode ligne qui est compétitive avec l'approche N / CCI, mais vous devez savoir quelque chose sur vos données. Supposons que vous disposiez d'une colonne contenant la différence de
RangeFrom
etRangeTo
et que vous l'ayez indexée avecRangeFrom
:Si vous connaissiez toutes les valeurs distinctes de,
DiffOfColumns
vous pouvez effectuer une recherche de chaque valeur deDiffOfColumns
avec un filtre de plageRangeTo
pour obtenir toutes les données pertinentes. Par exemple, si nous savons queDiffOfColumns
= 2, les seules valeurs autorisées pourRangeFrom
sont 49999998, 49999999 et 50000000. La récursivité peut être utilisée pour obtenir toutes les valeurs distinctes deDiffOfColumns
et cela fonctionne bien pour votre ensemble de données car il n'y en a que 256. La requête ci-dessous prend environ 6 ms sur ma machine:Vous pouvez voir la partie récursive habituelle ainsi que la recherche d'index pour chaque valeur distincte:
Le défaut de cette approche est qu'elle commence à ralentir lorsqu'il y a trop de valeurs distinctes pour
DiffOfColumns
. Faisons le même test, mais utilisons à laCRYPT_GEN_RANDOM(2)
place deCRYPT_GEN_RANDOM(1)
.La même requête trouve maintenant 65536 lignes de la partie récursive et prend 823 ms de CPU sur ma machine. Il y a des attentes PAGELATCH_SH et d'autres mauvaises choses en cours. Je peux améliorer les performances en regroupant les valeurs de diff pour garder le nombre de valeurs uniques sous contrôle et en ajustant le regroupement dans le
CROSS APPLY
. Pour cet ensemble de données, j'essaierai 256 compartiments:Une façon d'éviter d'avoir des lignes supplémentaires (maintenant je compare à une valeur arrondie au lieu de la vraie valeur) est de filtrer sur
RangeTo
:La requête complète prend maintenant 6 ms sur ma machine.
la source
Une autre façon de représenter une plage serait de représenter des points sur une ligne.
Ce qui suit migre toutes les données dans une nouvelle table avec la plage représentée comme un
geometry
type de données.La requête équivalente pour trouver des plages contenant la valeur
50,000,000
est ci-dessous.Les lectures pour cela montrent une amélioration par
10,951
rapport à la requête d'origine.Cependant, il n'y a pas d'amélioration significative par rapport à l'original en termes de temps écoulé . Les résultats d'exécution typiques sont de 250 ms contre 252 ms.
Le plan d'exécution est plus complexe que ci-dessous
Le seul cas où la réécriture fonctionne de manière fiable mieux pour moi est avec un cache froid.
Tellement décevant dans ce cas et difficile de recommander cette réécriture mais la publication de résultats négatifs peut également être utile.
la source
En hommage à nos nouveaux robots seigneurs, j'ai décidé de voir si l'une des nouvelles fonctionnalités R et Python pourrait nous aider ici. La réponse est non, du moins pour les scripts que j'ai pu travailler et renvoyer des résultats corrects. Si quelqu'un avec une meilleure connaissance vient, eh bien, n'hésitez pas à me fesser. Mes tarifs sont raisonnables.
Pour ce faire, j'ai configuré une machine virtuelle avec 4 cœurs et 16 Go de RAM, pensant que cela suffirait pour gérer un ensemble de données de ~ 200 Mo.
Commençons par la langue qui n'existe pas à Boston!
R
Ce fut un mauvais moment.
Le plan d'exécution est assez inintéressant, bien que je ne sache pas pourquoi l'opérateur intermédiaire doit nous appeler des noms.
Ensuite, codage avec des crayons!
Python
Juste au moment où vous pensiez que cela ne pouvait pas être pire que R:
Un autre plan d'exécution grossier :
Hmm et Hmmer
Jusqu'à présent, je ne suis pas impressionné. J'ai hâte de supprimer cette machine virtuelle.
la source
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 ));
mais les performances ne sont pas excellentes. J'utilise R pour des choses que vous ne pouvez pas faire en SQL, dites si vous voulez prédire quelque chose.J'ai trouvé une assez bonne solution en utilisant une colonne calculée, mais elle n'est bonne que pour une seule valeur. Cela étant dit, si vous avez une valeur magique, c'est peut-être suffisant.
En commençant par votre échantillon donné, puis en modifiant le tableau:
La requête devient simplement:
Qui renvoie les mêmes résultats que votre requête de départ. Avec les plans d'exécution désactivés, voici les statistiques (tronquées par souci de concision):
Et voici le plan de requête :
la source
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
travaillerait. Et la requête l'SELECT * FROM MyTable WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000;
utilise - donc pas grand besoin du pauvre CurtisMa solution est basée sur l'observation que l'intervalle a une largeur maximale connue W . Pour les exemples de données, il s'agit d'un octet ou de 256 entiers. Par conséquent , pour une valeur de paramètre de recherche donné P nous savons que le plus petit RangeFrom qui peut être dans le jeu de résultats est P - W . L'ajout de cela au prédicat donne
Compte tenu de la configuration et de la requête d'origine, ma machine (Windows 10 64 bits, i7 hyperthread 4 cœurs, 2,8 GHz, 16 Go de RAM) renvoie 13 lignes. Cette requête utilise une recherche d'index parallèle de l'index (RangeFrom, RangeTo). La requête révisée effectue également une recherche d'index parallèle sur le même index.
Les mesures pour les requêtes originales et révisées sont
Pour la requête d'origine, le nombre de lignes lues est égal au nombre de lignes inférieures ou égales à @P. L'optimiseur de requêtes (QO) n'a pas d'autre choix que de les lire toutes car il ne peut pas déterminer à l'avance si ces lignes satisferont le prédicat. L'index multi-colonnes sur (RangeFrom, RangeTo) n'est pas utile pour éliminer les lignes qui ne correspondent pas à RangeTo car il n'y a pas de corrélation entre la première clé d'index et la seconde qui peut être appliquée. Par exemple, la première ligne peut avoir un petit intervalle et être éliminée tandis que la deuxième ligne a un grand intervalle et est renvoyée, ou vice versa.
Dans une tentative infructueuse, j'ai essayé de fournir cette certitude grâce à une contrainte de vérification:
Cela n'a fait aucune différence.
En incorporant mes connaissances externes de la distribution des données dans le prédicat, je peux faire en sorte que le QO ignore les lignes RangeFrom de faible valeur, qui ne peuvent jamais faire partie de l'ensemble de résultats, et traverse la colonne de tête de l'index vers les lignes admissibles. Cela apparaît dans les différents prédicats de recherche pour chaque requête.
Dans un argument miroir, la limite supérieure de RangeTo est P + W . Cela n'est cependant pas utile, car il n'existe aucune corrélation entre RangeFrom et RangeTo qui permettrait à la colonne de fin d'un index multi-colonnes d'éliminer les lignes. Par conséquent, il n'y a aucun avantage à ajouter cette clause à la requête.
Cette approche tire l'essentiel de ses avantages de la petite taille de l'intervalle. À mesure que la taille d'intervalle possible augmente, le nombre de lignes de faible valeur ignorées diminue, bien que certaines soient toujours ignorées. Dans le cas limite, avec un intervalle aussi grand que la plage de données, cette approche n'est pas pire que la requête originale (qui est un confort froid, je l'admets).
Je m'excuse pour toutes les erreurs ponctuelles pouvant exister dans cette réponse.
la source