Rendimiento de TSQL: ÚNASE al valor ENTRE min y max

10

Tengo dos mesas en las que almaceno:

  • un rango de IP - tabla de búsqueda de países
  • una lista de solicitudes provenientes de diferentes IP

Las IP se almacenaron como bigints para mejorar el rendimiento de búsqueda.

Esta es la estructura de la tabla:

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
)

Quiero obtener el desglose de la solicitud por país, por lo que realizo la siguiente consulta:

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

Tengo muchos registros en las tablas: alrededor de 200,000 IP2Countryy algunos millones Request, por lo que la consulta lleva un tiempo.

Mirando el plan de ejecución, la parte más costosa es una Búsqueda de índice agrupado en el índice PK_IP2Country, que se ejecuta muchas veces (el número de filas en Solicitud).

Además, algo de lo que me siento un poco extraño es la left join ip2country ic on r.IP between ic.begin_num and ic.end_numparte (no sé si hay una mejor manera de realizar la búsqueda).

La estructura de la tabla, algunos datos de muestra y consultas están disponibles en SQLFiddle: http://www.sqlfiddle.com/#!3/a463e/3 (desafortunadamente no creo que pueda insertar muchos registros para reproducir el problema, pero esto con suerte da una idea).

No soy (obviamente) un experto en rendimiento / optimizaciones de SQL, así que mi pregunta es: ¿hay alguna forma obvia de mejorar esta estructura / consulta en cuanto al rendimiento que me falta?

Cristian Lupascu
fuente
2
¿Se puede asignar una dirección IP a varios países? Si no, puede reducir su PK a solo begin_num. También tengo que unirme con A BETWEEN B AND Cbastante frecuencia, y tengo curiosidad por saber si hay una manera de lograr esto sin tediosas uniones RBAR.
Jon of All Trades
1
Es un poco fuera de tema para su pregunta, pero consideraría hacer columnas calculadas begin_ipy end_ippersistentes, para evitar la posibilidad de que el texto y los números se desincronicen de alguna manera.
Jon of All Trades
@ w0lf: ¿hay rangos superpuestos en ip2country (begin_num, end_num)?
ypercubeᵀᴹ
@JonofAllTrades normalmente una IP debe pertenecer a un solo país, por lo que creo que su idea de una consulta como give me the first record that has a begin_num < ip in asc order of begin_num(corríjame si me equivoco) podría ser válida y mejorar el rendimiento.
Cristian Lupascu
1
@ w0lf: Mi impresión es que eso es básicamente lo que el servidor está haciendo en un caso como este, porque primero escanea begin_num, luego escanea end_numdentro de ese conjunto y solo encuentra un registro.
Jon of All Trades

Respuestas:

3

Necesitas un índice adicional. En su ejemplo de Fiddle agregué:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Que lo cubre para la tabla de solicitud y obtiene una búsqueda de índice en lugar de una exploración de índice agrupado.

Vea cómo eso lo mejora y hágamelo saber. Supongo que ayudará bastante ya que el escaneo en ese índice es seguro que no es barato.

JNK
fuente
No sé por qué, pero los resultados parecen ser diferentes (en SQLFiddle)
Cristian Lupascu
@ w0lf: son diferentes (probablemente) porque ambos están insertando datos aleatorios en las tablas.
ypercubeᵀᴹ
@ypercube seguramente esa es la causa. He hecho tantas cosas últimamente que olvidé que los datos eran aleatorios. Lo siento.
Cristian Lupascu
2

Siempre existe el enfoque de la fuerza bruta: podría explotar su mapa IP. Únase a una tabla de números contra su mapa existente para crear un registro por dirección IP. Eso es solo 267K registros basados ​​en sus datos de Fiddle, sin ningún 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

Esto haría las búsquedas más simples y, con suerte, más rápidas. Esto solo tiene sentido si realiza relativamente pocas actualizaciones ip2country, por supuesto.

¡Espero que alguien más tenga una mejor solución!

Jon de todos los oficios
fuente
Todo el conjunto de datos produciría más de 5 mil millones de registros, por lo que no creo que lo haga. Pero esta es una buena idea, sin embargo; Estoy seguro de que es factible en muchos casos similares. +1
Cristian Lupascu
0

Prueba esto:

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
fuente
gracias, he probado tu enfoque, pero parece ser más caro que la consulta inicial
Cristian Lupascu
¿Cuántas filas tienes en cada tabla? Me gustaría reproducir la escala de su problema en mi DB e intentar resolverlo sin agregar un índice :)
Vince Pergolizzi
alrededor de 200,000 en IP2 Country y unos pocos millones (posiblemente decenas de millones en el futuro cercano) en Request. Creo que si lo resuelves sin índices, mereces un título de "DBA del año" :)
Cristian Lupascu