Performances TSQL - JOIN sur une valeur entre min et max


10

J'ai deux tables dans lesquelles je stocke:

  • une plage d'adresses IP - table de recherche par pays
  • une liste de requêtes provenant de différentes IP

Les adresses IP ont été stockées sous forme de bigints pour améliorer les performances de recherche.

Voici la structure du tableau:

create table [dbo].[ip2country](
    [begin_ip] [varchar](15) NOT NULL,
    [end_ip] [varchar](15) NOT NULL,
    [begin_num] [bigint] NOT NULL,
    [end_num] [bigint] NOT NULL,
    [IDCountry] [int] NULL,
    constraint [PK_ip2country] PRIMARY KEY CLUSTERED 
    (
        [begin_num] ASC,
        [end_num] ASC
    )
)

create table Request(
    Id int identity primary key, 
    [Date] datetime, 
    IP bigint, 
    CategoryId int
)

Je souhaite obtenir la répartition des demandes par pays, j'exécute donc la requête suivante:

select 
    ic.IDCountry,
    count(r.Id) as CountryCount
from Request r
left join ip2country ic 
  on r.IP between ic.begin_num and ic.end_num
where r.CategoryId = 1
group by ic.IDCountry

J'ai beaucoup d'enregistrements dans les tableaux: environ 200 000 IP2Countryet quelques millions Request, la requête prend donc un certain temps.

En regardant le plan d'exécution, la partie la plus coûteuse est une recherche d'index cluster sur l'index PK_IP2Country, qui est exécutée plusieurs fois (le nombre de lignes dans la demande).

En outre, quelque chose qui me semble un peu étrange est la left join ip2country ic on r.IP between ic.begin_num and ic.end_numpartie (je ne sais pas s'il existe une meilleure façon d'effectuer la recherche).

La structure de la table, des exemples de données et des requêtes sont disponibles dans SQLFiddle: http://www.sqlfiddle.com/#!3/a463e/3 (malheureusement, je ne pense pas pouvoir insérer de nombreux enregistrements pour reproduire le problème, mais cela donne, espérons-le, une idée).

Je ne suis (évidemment) pas un expert en performances / optimisations SQL, donc ma question est la suivante: y a-t-il des façons évidentes d'améliorer cette structure / requête en termes de performances qui me manquent?


2
Une adresse IP peut-elle correspondre à plusieurs pays? Sinon, vous pouvez limiter votre PK à juste begin_num. Je dois également participer A BETWEEN B AND Cassez souvent, et je suis curieux de savoir s'il existe un moyen d'y parvenir sans les fastidieuses jointures RBAR.
Jon de tous les métiers

1
C'est un peu hors sujet pour votre question, mais j'envisagerais de créer des colonnes calculées begin_ipet end_ippersistantes, pour éviter que le texte et les chiffres ne se désynchronisent d'une manière ou d'une autre.
Jon de tous les métiers

@ w0lf: existe-t-il des plages qui se chevauchent ip2country (begin_num, end_num)?
ypercubeᵀᴹ

@JonofAllTrades normalement une adresse IP devrait appartenir à un seul pays, donc je pense que votre idée d'une requête comme give me the first record that has a begin_num < ip in asc order of begin_num(corrigez-moi si je me trompe) pourrait être valide et améliorer les performances.
Cristian Lupascu

1
@ w0lf: J'ai l'impression que c'est essentiellement ce que fait le serveur dans un cas comme celui-ci, car il analyse d'abord begin_num, puis analyse end_numdans cet ensemble et ne trouve qu'un seul enregistrement.
Jon de tous les métiers

Réponses:


3

Vous avez besoin d'un index supplémentaire. Dans votre exemple Fiddle, j'ai ajouté:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Ce qui vous couvre pour la table de demande et obtient une recherche d'index au lieu d'une analyse d'index en cluster.

Voyez comment cela l'améliore et faites le moi savoir. J'imagine que ça va aider un peu puisque l'analyse sur cet index est que je ne suis certainement pas bon marché.


Je ne sais pas pourquoi, mais les résultats semblent différents (dans SQLFiddle)
Cristian Lupascu

@ w0lf: ils sont différents (probablement) parce que vous insérez tous deux des données aléatoires dans les tables.
ypercubeᵀᴹ

@ypercube c'est sûrement la cause. J'ai fait tellement de choses ces derniers temps que j'ai oublié que les données étaient aléatoires. Pardon.
Cristian Lupascu

2

Il y a toujours l'approche par force brute: vous pouvez exploser votre carte IP. Joignez un tableau de nombres à votre carte existante pour créer un enregistrement par adresse IP. Cela ne représente que 267 000 enregistrements basés sur vos données Fiddle, aucun problème du tout.

CREATE TABLE IPLookup
  (
  IP  BIGINT PRIMARY KEY,
  CountryID  INT
  )
INSERT INTO IPLookup (IP, CountryID)
  SELECT
    N.Number, Existing.IDCountry
  FROM
    ip2country AS Existing
    INNER JOIN Numbers AS N ON N.Number BETWEEN Existing.begin_num AND Existing.end_num

Cela rendrait les recherches plus simples et, espérons-le, plus rapides. Cela n'a de sens que si vous effectuez relativement peu de mises à jour ip2country, bien sûr.

J'espère que quelqu'un d'autre a une meilleure solution!


L'ensemble des données produirait plus de 5 milliards d'enregistrements, donc je ne pense pas que je le ferai. Mais c'est quand même une bonne idée; Je suis sûr que c'est faisable dans de nombreux cas similaires. +1
Cristian Lupascu

0

Essaye ça:

SELECT ic.IDCountry,
        COUNT(r.Id) AS CountryCount
FROM Request r
INNER JOIN (SELECT begin_num+NUMS.N [IP], IDCountry 
            FROM ip2country
            CROSS JOIN (SELECT TOP(SELECT ABS(MAX(end_num-begin_num)) FROM ip2country) ROW_NUMBER() OVER(ORDER BY sc.name)-1 [N]
                        FROM sys.columns sc) NUMS
            WHERE begin_num+NUMS.N <= end_num) ic
ON r.IP = ic.IP
WHERE r.CategoryId = 1
GROUP BY ic.IDCountry

merci, j'ai essayé votre approche, mais elle semble être plus chère que la requête initiale
Cristian Lupascu

Combien de lignes avez-vous dans chaque tableau? Je voudrais reproduire l'échelle de votre problème sur ma base de données et essayer de le résoudre sans ajouter d'index :)
Vince Pergolizzi

environ 200 000 dans IP2Country et quelques millions (peut-être des dizaines de millions dans un avenir proche) dans Request. Je pense que si vous le résolvez sans index, vous méritez un titre de "DBA de l'année" :)
Cristian Lupascu
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.