TSQL-Leistung - JOIN auf Wert ZWISCHEN min und max

10

Ich habe zwei Tabellen, in denen ich speichere:

  • eine IP-Bereich - Länder-Nachschlagetabelle
  • Eine Liste der Anforderungen, die von verschiedenen IPs stammen

Die IPs wurden als bigints gespeichert , um die Suchleistung zu verbessern.

Dies ist die Tabellenstruktur:

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
)

Ich möchte die Aufschlüsselung der Anfragen nach Ländern erhalten, daher führe ich die folgende Abfrage durch:

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

Ich habe viele Datensätze in den Tabellen: ungefähr 200.000 Zoll IP2Countryund einige Millionen Zoll Request, daher dauert die Abfrage eine Weile.

Mit Blick auf den Ausführungsplan ist der teuerste Teil ein Clustered Index Seek für den Index PK_IP2Country, der viele Male ausgeführt wird (die Anzahl der Zeilen in Request).

Außerdem ist etwas, bei dem ich mich etwas seltsam fühle, der left join ip2country ic on r.IP between ic.begin_num and ic.end_numTeil (ich weiß nicht, ob es einen besseren Weg gibt, die Suche durchzuführen).

Die Tabellenstruktur, einige Beispieldaten und die Abfrage sind in SQLFiddle verfügbar: http://www.sqlfiddle.com/#!3/a463e/3 (leider glaube ich nicht, dass ich viele Datensätze einfügen kann, um das Problem zu reproduzieren, aber dies hoffentlich gibt eine Idee).

Ich bin (offensichtlich) kein Experte für SQL-Leistung / -Optimierungen, daher lautet meine Frage: Gibt es offensichtliche Möglichkeiten, wie diese Struktur / Abfrage in Bezug auf die Leistung verbessert werden kann, die mir fehlen?

Cristian Lupascu
quelle
2
Kann eine IP-Adresse mehreren Ländern zugeordnet werden? Wenn nicht, können Sie Ihre PK auf nur einschränken begin_num. Ich muss auch A BETWEEN B AND Cziemlich oft mitmachen , und ich bin gespannt, ob es einen Weg gibt, dies ohne langwierige RBAR-Beitritte zu erreichen.
Jon of All Trades
1
Ihre Frage ist ein wenig unangebracht, aber ich würde in Betracht ziehen, berechnete Spalten zu erstellen begin_ipund end_ipbeizubehalten, um zu verhindern, dass Text und Zahlen irgendwie nicht mehr synchron sind.
Jon of All Trades
@ w0lf: Gibt es überlappende Bereiche in ip2country (begin_num, end_num)?
Ypercubeᵀᴹ
@JonofAllTrades Normalerweise sollte eine IP zu einem einzelnen Land gehören, daher denke ich, dass Ihre Vorstellung von einer Abfrage wie give me the first record that has a begin_num < ip in asc order of begin_num(korrigieren Sie mich, wenn ich falsch liege ) gültig sein und die Leistung verbessern könnte.
Cristian Lupascu
1
@ w0lf: Mein Eindruck ist, dass der Server in einem solchen Fall im Grunde das tut, weil er zuerst begin_numnach end_numdiesem Satz scannt , dann innerhalb dieses Satzes nach ihm scannt und nur einen Datensatz findet.
Jon of All Trades

Antworten:

3

Sie benötigen einen zusätzlichen Index. In Ihrem Geigenbeispiel habe ich hinzugefügt:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Was Sie für die Anforderungstabelle abdeckt und eine Indexsuche anstelle eines Clustered-Index-Scans erhält.

Sehen Sie, wie sich das verbessert, und lassen Sie es mich wissen. Ich vermute, es wird ziemlich viel helfen, da der Scan dieses Index sicher nicht billig ist.

JNK
quelle
Ich weiß nicht warum, aber die Ergebnisse scheinen anders zu sein (in SQLFiddle)
Cristian Lupascu
@ w0lf: Sie sind unterschiedlich (wahrscheinlich), weil Sie beide zufällige Daten in die Tabellen einfügen.
Ypercubeᵀᴹ
@ypercube sicherlich ist das die Ursache. Ich habe in letzter Zeit so viele Dinge getan, dass ich vergessen habe, dass die Daten zufällig waren. Es tut uns leid.
Cristian Lupascu
2

Es gibt immer den Brute-Force-Ansatz: Sie könnten Ihre IP-Karte explodieren lassen. Verbinden Sie eine Zahlentabelle mit Ihrer vorhandenen Karte, um einen Datensatz pro IP-Adresse zu erstellen. Das sind nur 267.000 Datensätze basierend auf Ihren Fiddle-Daten, überhaupt kein Problem.

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

Dies würde die Suche einfacher und hoffentlich schneller machen. Dies ist ip2countrynatürlich nur dann sinnvoll, wenn Sie relativ wenige Updates vornehmen .

Ich hoffe jemand anderes hat eine bessere Lösung!

Jon aller Berufe
quelle
Der gesamte Datensatz würde mehr als 5 Milliarden Datensätze produzieren, also denke ich nicht, dass ich es tun werde. Aber das ist trotzdem eine schöne Idee; Ich bin sicher, dass es in vielen ähnlichen Fällen machbar ist. +1
Cristian Lupascu
0

Versuche dies:

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
Vince Pergolizzi
quelle
Danke, ich habe Ihren Ansatz ausprobiert, aber er scheint teurer zu sein als die erste Abfrage
Cristian Lupascu
Wie viele Zeilen haben Sie in jeder Tabelle? Ich möchte das Ausmaß Ihres Problems auf meiner Datenbank reproduzieren und versuchen, es zu lösen, ohne einen Index hinzuzufügen :)
Vince Pergolizzi
etwa 200.000 in IP2Country und einige Millionen (möglicherweise mehrere zehn Millionen in naher Zukunft) auf Anfrage. Ich denke, wenn Sie es ohne Indizes lösen, verdienen Sie einen "DBA des Jahres" -Titel :)
Cristian Lupascu