Prestazioni TSQL - ISCRIVITI al valore TRA min e max


10

Ho due tavoli in cui conservo:

  • un intervallo IP - tabella di ricerca per paese
  • un elenco di richieste provenienti da IP diversi

Gli IP sono stati archiviati come bigints per migliorare le prestazioni di ricerca.

Questa è la struttura della tabella:

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
)

Voglio ottenere la suddivisione della richiesta per paese, quindi eseguo la seguente query:

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

Ho molti record nelle tabelle: circa 200.000 in IP2Countrye pochi milioni in Request, quindi la query richiede un po 'di tempo.

Guardando il piano di esecuzione, la parte più costosa è una ricerca dell'indice cluster sull'indice PK_IP2Country, che viene eseguita più volte (il numero di righe nella richiesta).

Inoltre, qualcosa di cui mi sento un po 'strano è la left join ip2country ic on r.IP between ic.begin_num and ic.end_numparte (non so se c'è un modo migliore per eseguire la ricerca).

La struttura della tabella, alcuni dati di esempio e query sono disponibili in SQLFiddle: http://www.sqlfiddle.com/#!3/a463e/3 (purtroppo non penso di poter inserire molti record per riprodurre il problema, ma questo spero che abbia un'idea).

Non sono (ovviamente) un esperto di prestazioni / ottimizzazioni SQL, quindi la mia domanda è: ci sono modi ovvi in ​​cui questa struttura / query può essere migliorata dal punto di vista delle prestazioni e che mi manca?


2
Un indirizzo IP può essere mappato a più paesi? Altrimenti, puoi restringere il tuo PK a solo begin_num. Devo anche unirmi A BETWEEN B AND Cabbastanza spesso e sono curioso di sapere se esiste un modo per raggiungere questo obiettivo senza noiosi join RBAR.
Jon of All Trades,

1
È un po 'fuori tema per la tua domanda, ma prenderei in considerazione la creazione begin_ipe il end_ippersistere di colonne calcolate, per evitare in qualche modo la possibilità che il testo e i numeri si discostino.
Jon of All Trades,

@ w0lf: ci sono intervalli sovrapposti in ip2country (begin_num, end_num)?
ypercubeᵀᴹ

@JonofAllTrades normalmente un IP dovrebbe appartenere a un singolo paese, quindi penso che la tua idea di una query come give me the first record that has a begin_num < ip in asc order of begin_num(correggimi se sbaglio) potrebbe essere valida e migliorare le prestazioni.
Cristian Lupascu,

1
@ w0lf: Le mie impressioni sono che è sostanzialmente ciò che il server sta facendo in un caso come questo, perché prima scansiona begin_num, quindi scansiona end_numall'interno di quel set e trova solo un record.
Jon of All Trades,

Risposte:


3

Hai bisogno di un indice aggiuntivo. Nel tuo esempio di violino ho aggiunto:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Che copre la tabella delle richieste e ottiene una ricerca dell'indice anziché una scansione dell'indice in cluster.

Guarda come lo migliora e fammi sapere. Immagino che ti aiuterà un po 'dal momento che la scansione su quell'indice è sicuro che non è economico.


Non so perché, ma i risultati sembrano diversi (in SQLFiddle)
Cristian Lupascu,

@ w0lf: sono diversi (probbali) perché entrambi stanno inserendo dati casuali nelle tabelle.
ypercubeᵀᴹ

@ypercube sicuramente è questa la causa. Ultimamente ho fatto così tante cose che ho dimenticato che i dati erano casuali. Scusate.
Cristian Lupascu,

2

C'è sempre l'approccio della forza bruta: potresti esplodere la tua mappa IP. Unisci una tabella numerica alla tua mappa esistente per creare un record per indirizzo IP. Sono solo 267K i record basati sui dati di Fiddle, nessun problema.

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

Ciò renderebbe le ricerche più semplici e, si spera, più veloci. Ciò ha senso solo se si effettuano relativamente pochi aggiornamenti ip2country, ovviamente.

Spero che qualcun altro abbia una soluzione migliore!


L'intero set di dati produrrebbe più di 5 miliardi di dischi, quindi non credo che lo farò. Questa è comunque una bella idea; Sono sicuro che è fattibile in molti casi simili. +1
Cristian Lupascu,

0

Prova questo:

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

grazie, ho provato il tuo approccio, ma sembra essere più costoso della domanda iniziale
Cristian Lupascu,

Quante righe hai in ogni tabella? Vorrei riprodurre l'entità del tuo problema sul mio DB e provare a risolvere senza aggiungere un indice :)
Vince Pergolizzi,

circa 200.000 in IP2Country e pochi milioni (forse decine di milioni nel prossimo futuro) in Request. Penso che se lo risolvi senza indici meriti un titolo "DBA dell'anno" :)
Cristian Lupascu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.