7
votes

Performances optimales pour rejoindre la plage de valeurs

J'ai une table vraiment grosse qui contient des représentations entière des adresses IP et une deuxième table qui a démarré et finissant de gammes de représentations d'entiers d'adresses IP. La deuxième table est utilisée pour renvoyer le pays selon Plusieurs articles sur Stackoverflow . Bien que cela renvoie les résultats requis, la performance est relativement pauvre. Existe-t-il une alternative plus performante pour rejoindre une gamme? Vous trouverez ci-dessous un exemple d'ensemble de code indiquant la manière dont la jointure fonctionne actuellement: xxx


11 commentaires

J'ai édité votre titre. S'il vous plaît voir, " Les questions incluent" Tags "dans leurs titres? ", où le consensus est "non, ils devraient ne pas".


Travaille pour moi. Merci.


Avez-vous des index dans vos tables réelles? Parce que votre script de test ne crée pas de manière probablement que tous les conseils donnés seront pour vous d'ajouter des index.


@Franciscosoto. . . Le script de test fait d'index. C'est une chose que la clé primaire fait.


.... tu ne dis pas? Je voulais dire Boh les colonnes en question. RangestArtValue et gammeValue. S'il a plus index, il devrait le rendre évident pour que les gens puissent donner la meilleure réponse.


@Utilisateur enregistré . . . Quelle est la plus petite granularité de la cartographie? Êtes-vous en train de taper des adresses de type D? La cartographie est-elle limitée au type A et B?


@Francisco Soto. . . Je n'ai pas inclus plus d'index. J'ai testé, y compris un indice unique non clusterisé strictement pour le gammeValue, mais cela n'a pas modifié les performances du code de test ni des tables réelles.


@Gordon linoff. . . Les tables vont au 4ème octet. ADRESSE IP ALLOCATIONS Passer à travers les pays du 4ème octet en 169 instances basées sur les données Software77.net.


Les gammes sont-elles toujours non-chevaucheuses et entièrement connues à l'avance?


Les chaînes ne se chevauchent pas, mais elles changent régulièrement. Je ne sais pas ce que "complètement connu à l'avance" signifie.


Laissez-moi reformuler: une table contenant toutes les gammes possibles est relativement petite (disons max 10 ** 6 lignes)


4 Réponses :


0
votes

Ceci est presque certainement un problème d'indexation. Vous avez actuellement un index sur le rangestarvalue (touche primaire), mais pas sur le gamme rangeendValue , il est donc probable qu'il doit faire une analyse complète sur la deuxième colonne après la rétrécissement de la première. Essayez d'indexation de la plage et voyez comment cela l'affecte.

Je ne suis pas bien versé dans les qualités de performance du entre la clause , mais vous pouvez garantir que cela ne peut pas être un problème en écrivant les deux côtés de la comparaison avec des contrôles d'inégalité.


Également dans ce script de test, vous sélectionnez chaque ligne de la table de base, que je suppose que vous ne faites pas dans votre DB de production?


1 commentaires

J'ai essayé, y compris la rangeendValue dans le cadre de la clé primaire et comme un indice distinct de celui des tableaux réels et dans le script de test fournis, mais cela ne modifie de quelque manière que ce soit le plan de requête ou les performances. J'ai également essayé, y compris Créer une index ix_rangueendvalue sur #Rangelookuptable (gammeValue); Et cela a changé le plan de requête pour utiliser une recherche d'index non clustered au lieu d'une demande d'index en cluster, mais le coût réel était identique à la fois dans le script de test et avec la table réelle.



0
votes

Le problème est que votre table de recherche a des tuiles non chevaucheuses (gammes) d'adresses. Cependant, SQL Server ne reconnaît probablement pas cela. Donc, lorsque vous avez ipaddress entre A et B , il doit analyser l'intégralité de l'index à partir du début et de la fin avec A.

Je ne sais pas s'il y a un moyen d'expliquer ce que la Le tableau fait vraiment, de manière à ce que l'optimiseur passe au premier enregistrement approprié de l'indice. Il est possible que quelque chose comme cela fonctionne: xxx

ceci pourrait "tromper" l'optimiseur à la recherche d'un seul enregistrement dans l'index. Les données de votre échantillon sont trop petites pour indiquer si cela aurait un effet sur la performance.

Une autre approche consiste à utiliser une jointure EQUI pour arrêter la recherche. Dans chaque table, ajoutez la partie Typea de l'adresse (le premier octet). Cela peut être soit redondant avec le deuxième champ avec l'adresse complète ou vous pouvez mettre les trois autres octets dans le champ suivant. Toute liste IP qui couvre plusieurs adresses Typea devrait être divisée en entrées distinctes.

Faites ce champ la première colonne de l'index avec l'adresse (ou le reste de l'adresse) comme deuxième partie du primaire. clé. Vous pouvez utiliser des contraintes pour créer une clé primaire avec plusieurs colonnes.

La requête ressemblerait alors à: xxx

La balayage de l'index serait alors limitée que aux valeurs avec le même premier octet. Bien sûr, vous pouvez également étendre ceci à TAYEAB, en utilisant les deux premiers octets.


0 commentaires

1
votes

Si la situation spécifique permet de contenir des données de normalisées fortes> de la table dans une table, puis d'interroger à partir de cette table plutôt que de la table de base normalisée, une durée de récupération très rapide pourrait être obtenue. Le plan d'exécution de la requête indique 2x gain dans la sélection, même avec ces données d'échantillon de 3 lignes.

Une telle approche serait possible dans un scénario ayant relativement moins d'écrivies et plus d'opérations de lecture. La jointure devra être exécutée uniquement lors de la mise à jour des données; Les tests avec des données réelles montreront combien (ou si tout le tout!) L'amélioration est réellement réalisée dans l'image du système global (UPDATE + SELECT). P>

code d'échantillon, ainsi que les captures d'écran du plan d'exécution résultant pour le Sélectionnez les instructions ci-dessous. P>

CREATE TABLE #BaseTable
    ( SomeIntegerValue INT PRIMARY KEY);

INSERT INTO #BaseTable (SomeIntegerValue)
SELECT SomeIntegerValue
FROM (VALUES
    (123), (456), (789)) Data (SomeIntegerValue);

CREATE TABLE #RangeLookupTable
    ( RangeStartValue INT PRIMARY KEY
    , RangeEndValue INT NOT NULL);

INSERT INTO #RangeLookupTable (RangeStartValue, RangeEndValue)
SELECT RangeStartValue, RangeEndValue
FROM (VALUES
      (0, 100), (101, 200), (201, 300)
    , (301, 400), (401, 500), (501, 600)
    , (701, 800), (901, 1000)) Data (RangeStartValue, RangeEndValue);

-- Alternative approach: Denormalized base table
CREATE TABLE #BaseTable2
    ( SomeIntegerValue INT PRIMARY KEY,
      RangeStartValue INT null,
      RangeEndValue INT NULL);

INSERT INTO #BaseTable2 (SomeIntegerValue)
SELECT SomeIntegerValue
FROM (VALUES
    (123), (456), (789)) Data (SomeIntegerValue);

UPDATE #BaseTable2
SET RangeStartValue = rlt.RangeStartValue,
    RangeEndValue = rlt.RangeEndValue
FROM #BaseTable2 bt2
JOIN #RangeLookupTable rlt
    ON bt2.SomeIntegerValue BETWEEN rlt.RangeStartValue AND rlt.RangeEndValue

-- The original: SELECT with a JOIN
SELECT *
FROM #BaseTable bt
JOIN #RangeLookupTable rlt
    ON bt.SomeIntegerValue BETWEEN rlt.RangeStartValue AND rlt.RangeEndValue

-- The alternative: SELECT from the denormalized base table
SELECT * from #BaseTable2;

GO


0 commentaires

0
votes

J'ai testé 15 approches distinctes que je pensais fonctionnerait et cette solution était la meilleure de loin:

SELECT bt.*
    , RangeStartValue = 
        (SELECT TOP 1 RangeStartValue
        FROM #RangeLookupTable rlt
        WHERE bt.SomeIntegerValue >= rlt.RangeStartValue
        ORDER BY rlt.RangeStartValue)
FROM #BaseTable bt;


0 commentaires